lescopr 0.1.2 → 1.0.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 +18 -1
- data/README.md +5 -5
- data/lib/lescopr/core/client.rb +30 -4
- data/lib/lescopr/modes/detector.rb +71 -0
- data/lib/lescopr/modes/direct.rb +106 -0
- data/lib/lescopr/modes/embedded.rb +137 -0
- data/lib/lescopr/transport/http_client.rb +12 -0
- data/lib/lescopr/version.rb +1 -1
- data/lib/lescopr.rb +28 -0
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3efeda7d85c0e939043d1e0c9d4537977d37152832209a96e486e2abed28a2b7
|
|
4
|
+
data.tar.gz: 05310bf63b8dd2fb3c0e0da98e6c5b5181c751482c8652a647ffd2b92c6067ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce9a7fc494e0946721ae00bc07e4ac82ebedec432f27423f27671b773d78bd1d6f1de4ce67b5dea83559bfca210f45dd60fce4d9b190f5437555bef25ecb6440
|
|
7
|
+
data.tar.gz: 77788f5050abf55f0d5dc62dbba9a2dbe867bea75e7dc8d110841858f9bb036617d40cbb476d847144870870dd17b0782e0706b031df50d29fc035f7d1e41d1f
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.0.0] — 2026-04-17
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Modes** — nouveau module `lib/lescopr/modes/` pour configurer finement le comportement du SDK (silent, verbose, strict)
|
|
18
|
+
- **Makefile** — workflow de release simplifié (`bump-patch`, `bump-minor`, `bump-major`, `release V=x.y.z`, `test`, `build`, `tag`, `push`)
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `lib/lescopr.rb` — API publique alignée avec les nouvelles capacités de configuration
|
|
22
|
+
- `lib/lescopr/core/client.rb` — intégration des modes
|
|
23
|
+
- `lib/lescopr/transport/http_client.rb` — retry logic renforcée
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Script de release : vérification de l'arbre git limitée au répertoire Ruby (`git status --porcelain .`) pour éviter les faux positifs dans les setups monorepo
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
14
30
|
## [0.1.2] — 2026-03-08
|
|
15
31
|
|
|
16
32
|
### Fixed
|
|
@@ -60,7 +76,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
76
|
|
|
61
77
|
---
|
|
62
78
|
|
|
63
|
-
[Unreleased]: https://github.com/Lescopr/lescopr-ruby/compare/
|
|
79
|
+
[Unreleased]: https://github.com/Lescopr/lescopr-ruby/compare/v1.0.0...HEAD
|
|
80
|
+
[1.0.0]: https://github.com/Lescopr/lescopr-ruby/releases/tag/v1.0.0
|
|
64
81
|
[0.1.2]: https://github.com/Lescopr/lescopr-ruby/compare/v0.1.1...v0.1.2
|
|
65
82
|
[0.1.1]: https://github.com/Lescopr/lescopr-ruby/compare/v0.1.0...v0.1.1
|
|
66
83
|
[0.1.0]: https://github.com/Lescopr/lescopr-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://rubygems.org/gems/lescopr)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
**Lescopr** is a zero-configuration Ruby monitoring SDK that automatically captures logs, errors, and exceptions from any Ruby project and streams them in real-time to the [Lescopr
|
|
8
|
+
**Lescopr** is a zero-configuration Ruby monitoring SDK that automatically captures logs, errors, and exceptions from any Ruby project and streams them in real-time to the [Lescopr app](https://app.lescopr.com).
|
|
9
9
|
|
|
10
10
|
Works out of the box with **Rails**, **Sinatra**, **Rack**, **Hanami**, **Grape**, and **plain Ruby**.
|
|
11
11
|
|
|
@@ -87,7 +87,7 @@ This detects your framework, registers the project with the Lescopr API, and wri
|
|
|
87
87
|
|
|
88
88
|
**Step 2 — Integrate into your application** (see [Framework Integration](#framework-integration) below).
|
|
89
89
|
|
|
90
|
-
**That's it.** All logs and exceptions are forwarded to the Lescopr
|
|
90
|
+
**That's it.** All logs and exceptions are forwarded to the Lescopr app automatically.
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
@@ -195,7 +195,7 @@ Your Ruby Application
|
|
|
195
195
|
https://api.lescopr.com
|
|
196
196
|
│
|
|
197
197
|
▼
|
|
198
|
-
Lescopr
|
|
198
|
+
Lescopr App
|
|
199
199
|
https://app.lescopr.com
|
|
200
200
|
```
|
|
201
201
|
|
|
@@ -276,8 +276,8 @@ To publish a new release:
|
|
|
276
276
|
|
|
277
277
|
| Channel | Link |
|
|
278
278
|
|---|---|
|
|
279
|
-
| 📖 Documentation | <https://
|
|
280
|
-
| 🌐
|
|
279
|
+
| 📖 Documentation | <https://lescopr.com/docs> |
|
|
280
|
+
| 🌐 App | <https://app.lescopr.com> |
|
|
281
281
|
| 📧 Email | <support@lescopr.com> |
|
|
282
282
|
| 🐛 Bug reports | <https://github.com/Lescopr/lescopr-ruby/issues> |
|
|
283
283
|
|
data/lib/lescopr/core/client.rb
CHANGED
|
@@ -5,13 +5,16 @@ module Lescopr
|
|
|
5
5
|
# Central SDK client — manages configuration, log queue, daemon lifecycle
|
|
6
6
|
# and the Ruby logger hook.
|
|
7
7
|
class Client
|
|
8
|
-
attr_reader :configuration, :sdk_id, :log_queue, :http_client, :sdk_logger
|
|
8
|
+
attr_reader :configuration, :sdk_id, :log_queue, :http_client, :sdk_logger,
|
|
9
|
+
:mode, :direct_mode
|
|
9
10
|
|
|
10
11
|
def initialize(configuration)
|
|
11
12
|
@configuration = configuration
|
|
12
13
|
@sdk_id = nil
|
|
13
14
|
@ready = false
|
|
14
15
|
@mutex = Mutex.new
|
|
16
|
+
@mode = nil # 'daemon' | 'embedded' | 'direct'
|
|
17
|
+
@direct_mode = nil # DirectMode or EmbeddedMode instance
|
|
15
18
|
|
|
16
19
|
@sdk_logger = Monitoring::Logger.new(debug: configuration.debug)
|
|
17
20
|
@log_queue = LogQueue.new
|
|
@@ -60,7 +63,20 @@ module Lescopr
|
|
|
60
63
|
@daemon.start
|
|
61
64
|
@ready = true
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
# Determine and start mode after being ready
|
|
67
|
+
@mode = Modes::Detector.detect
|
|
68
|
+
sdk_logger.info("SDK initialised — mode: #{@mode}, project: #{payload[:project_name]}, sdk_id: #{@sdk_id}")
|
|
69
|
+
|
|
70
|
+
case @mode
|
|
71
|
+
when 'direct'
|
|
72
|
+
@direct_mode = Modes::DirectMode.new(http_client)
|
|
73
|
+
@direct_mode.start
|
|
74
|
+
when 'embedded'
|
|
75
|
+
@direct_mode = Modes::EmbeddedMode.new(http_client)
|
|
76
|
+
@direct_mode.start
|
|
77
|
+
end
|
|
78
|
+
# 'daemon' uses existing DaemonRunner — no extra setup needed
|
|
79
|
+
|
|
64
80
|
true
|
|
65
81
|
rescue StandardError => e
|
|
66
82
|
sdk_logger.error("setup! failed: #{e.message}")
|
|
@@ -87,12 +103,22 @@ module Lescopr
|
|
|
87
103
|
environment: configuration.environment,
|
|
88
104
|
metadata: metadata
|
|
89
105
|
}
|
|
90
|
-
|
|
106
|
+
|
|
107
|
+
# Route to mode-specific transport
|
|
108
|
+
if @direct_mode
|
|
109
|
+
@direct_mode.add_log(entry)
|
|
110
|
+
else
|
|
111
|
+
log_queue.push(entry)
|
|
112
|
+
end
|
|
91
113
|
end
|
|
92
114
|
|
|
93
115
|
# Graceful shutdown — flush queue then stop daemon.
|
|
94
116
|
def shutdown!
|
|
95
|
-
@
|
|
117
|
+
if @direct_mode
|
|
118
|
+
@direct_mode.stop
|
|
119
|
+
else
|
|
120
|
+
@daemon.stop
|
|
121
|
+
end
|
|
96
122
|
@ready = false
|
|
97
123
|
sdk_logger.info("SDK shut down")
|
|
98
124
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lescopr
|
|
4
|
+
module Modes
|
|
5
|
+
##
|
|
6
|
+
# Auto-detect the optimal transport mode.
|
|
7
|
+
#
|
|
8
|
+
# Priority:
|
|
9
|
+
# 1. LESCOPR_MODE env var (daemon | embedded | direct)
|
|
10
|
+
# 2. Serverless / short-lived signals → direct
|
|
11
|
+
# 3. Multi-worker forked process → embedded
|
|
12
|
+
# 4. Container (Docker / K8s) → embedded
|
|
13
|
+
# 5. Daemon local active (checked by caller) → daemon
|
|
14
|
+
# 6. Fallback → embedded
|
|
15
|
+
#
|
|
16
|
+
# @return ['daemon', 'embedded', 'direct']
|
|
17
|
+
module Detector
|
|
18
|
+
SERVERLESS_SIGNALS = %w[
|
|
19
|
+
AWS_LAMBDA_FUNCTION_NAME
|
|
20
|
+
AWS_LAMBDA_RUNTIME_API
|
|
21
|
+
LAMBDA_TASK_ROOT
|
|
22
|
+
VERCEL
|
|
23
|
+
VERCEL_ENV
|
|
24
|
+
NETLIFY
|
|
25
|
+
NETLIFY_DEV
|
|
26
|
+
K_SERVICE
|
|
27
|
+
FUNCTION_NAME
|
|
28
|
+
FUNCTIONS_WORKER_RUNTIME
|
|
29
|
+
AZURE_FUNCTIONS_ENVIRONMENT
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
def self.detect
|
|
33
|
+
forced = ENV.fetch("LESCOPR_MODE", "").downcase
|
|
34
|
+
return forced if %w[daemon embedded direct].include?(forced)
|
|
35
|
+
|
|
36
|
+
return "direct" if serverless?
|
|
37
|
+
return "embedded" if multiworker_child?
|
|
38
|
+
return "embedded" if container?
|
|
39
|
+
|
|
40
|
+
# Caller checks whether the daemon is actually running
|
|
41
|
+
"daemon"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.serverless?
|
|
45
|
+
SERVERLESS_SIGNALS.any? { |s| ENV.key?(s) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.multiworker_child?
|
|
49
|
+
# Unicorn / Puma forked workers expose the master PID
|
|
50
|
+
return true if ENV["UNICORN_FD"] || ($0 && $0.include?("unicorn"))
|
|
51
|
+
return true if ENV["PUMA_WORKER"] || (defined?(Puma) && Puma.respond_to?(:cli_config))
|
|
52
|
+
return true if ENV["SIDEKIQ_WORKERS"] || (defined?(Sidekiq) && Sidekiq.server?)
|
|
53
|
+
false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.container?
|
|
57
|
+
return true if File.exist?("/.dockerenv")
|
|
58
|
+
return true if ENV["KUBERNETES_SERVICE_HOST"]
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
content = File.read("/proc/1/cgroup")
|
|
62
|
+
return true if content.include?("docker") || content.include?("kubepods")
|
|
63
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
64
|
+
# Not Linux or no access — not a container
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thread"
|
|
4
|
+
|
|
5
|
+
module Lescopr
|
|
6
|
+
module Modes
|
|
7
|
+
##
|
|
8
|
+
# DirectMode — in-memory queue + HTTPS batch flush to /logs/ingest.
|
|
9
|
+
#
|
|
10
|
+
# Designed for serverless / ephemeral Ruby processes:
|
|
11
|
+
# - AWS Lambda (ruby runtime)
|
|
12
|
+
# - Heroku one-off dynos
|
|
13
|
+
# - Short-lived Rake tasks or scripts
|
|
14
|
+
#
|
|
15
|
+
# Guarantees:
|
|
16
|
+
# - Periodic flush every FLUSH_INTERVAL seconds (background thread)
|
|
17
|
+
# - Guaranteed flush on process exit via at_exit hook
|
|
18
|
+
# - Re-queue on network error (up to MAX_QUEUE entries)
|
|
19
|
+
class DirectMode
|
|
20
|
+
FLUSH_INTERVAL = 15 # seconds
|
|
21
|
+
BATCH_SIZE = 100
|
|
22
|
+
MAX_QUEUE = 500
|
|
23
|
+
|
|
24
|
+
def initialize(http_client)
|
|
25
|
+
@http_client = http_client
|
|
26
|
+
@queue = []
|
|
27
|
+
@mutex = Mutex.new
|
|
28
|
+
@stop_event = false
|
|
29
|
+
@thread = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# ── Lifecycle ──────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
def start
|
|
35
|
+
install_exit_hook!
|
|
36
|
+
start_flush_thread!
|
|
37
|
+
|
|
38
|
+
Lescopr::Monitoring::Logger.new.info("[DIRECT] Mode direct actif (HTTPS batch)")
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stop
|
|
43
|
+
@stop_event = true
|
|
44
|
+
@thread&.kill
|
|
45
|
+
flush_sync
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# ── Public API ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
def add_log(entry)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
@queue << entry
|
|
53
|
+
if @queue.size > MAX_QUEUE
|
|
54
|
+
@queue = @queue.last(MAX_QUEUE / 2)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# ── Flush ──────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def flush_sync
|
|
62
|
+
loop do
|
|
63
|
+
batch = @mutex.synchronize { @queue.shift(BATCH_SIZE) }
|
|
64
|
+
break if batch.empty?
|
|
65
|
+
|
|
66
|
+
send_batch(batch)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def start_flush_thread!
|
|
73
|
+
@thread = Thread.new do
|
|
74
|
+
Thread.current.name = "lescopr-direct" rescue nil
|
|
75
|
+
until @stop_event
|
|
76
|
+
sleep(FLUSH_INTERVAL)
|
|
77
|
+
flush_sync
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
@thread.abort_on_exception = false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def install_exit_hook!
|
|
84
|
+
at_exit { flush_sync }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def send_batch(batch)
|
|
88
|
+
return if batch.empty?
|
|
89
|
+
|
|
90
|
+
result = @http_client.ingest_logs(batch)
|
|
91
|
+
|
|
92
|
+
if result
|
|
93
|
+
Lescopr::Monitoring::Logger.new.debug("[DIRECT] #{batch.size} log(s) envoyé(s)")
|
|
94
|
+
else
|
|
95
|
+
Lescopr::Monitoring::Logger.new.warn("[DIRECT] Flush échoué — remis en queue")
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
@queue = batch + @queue
|
|
98
|
+
if @queue.size > MAX_QUEUE
|
|
99
|
+
@queue = @queue.last(MAX_QUEUE / 2)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thread"
|
|
4
|
+
|
|
5
|
+
module Lescopr
|
|
6
|
+
module Modes
|
|
7
|
+
##
|
|
8
|
+
# EmbeddedMode — gRPC-less in-process transport for long-running Ruby apps.
|
|
9
|
+
#
|
|
10
|
+
# Designed for:
|
|
11
|
+
# - Docker / Kubernetes containers (no sidecar daemon)
|
|
12
|
+
# - Gunicorn-style forked multi-worker apps (Puma, Unicorn)
|
|
13
|
+
# - Any environment where running a separate daemon is impractical
|
|
14
|
+
#
|
|
15
|
+
# Behaviour:
|
|
16
|
+
# - Reuses the existing HTTP transport (no gRPC required)
|
|
17
|
+
# - Background thread flushes queue via HTTPS batch every FLUSH_INTERVAL s
|
|
18
|
+
# - Exponential backoff on connection errors (up to 12 attempts)
|
|
19
|
+
# - Exits cleanly via at_exit hook
|
|
20
|
+
class EmbeddedMode
|
|
21
|
+
FLUSH_INTERVAL = 30 # seconds between normal flushes
|
|
22
|
+
BATCH_SIZE = 100
|
|
23
|
+
MAX_QUEUE = 500
|
|
24
|
+
MAX_CONNECT_ATTEMPTS = 12
|
|
25
|
+
|
|
26
|
+
def initialize(http_client)
|
|
27
|
+
@http_client = http_client
|
|
28
|
+
@queue = []
|
|
29
|
+
@mutex = Mutex.new
|
|
30
|
+
@stop_event = false
|
|
31
|
+
@thread = nil
|
|
32
|
+
@connected = false
|
|
33
|
+
@logger = Lescopr::Monitoring::Logger.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# ── Lifecycle ──────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
def start
|
|
39
|
+
install_exit_hook!
|
|
40
|
+
start_worker_thread!
|
|
41
|
+
|
|
42
|
+
@logger.info("[EMBEDDED] Mode embedded actif (thread HTTP)")
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stop
|
|
47
|
+
@stop_event = true
|
|
48
|
+
@thread&.join(5)
|
|
49
|
+
flush_sync
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ── Public API ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
def add_log(entry)
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
@queue << entry
|
|
57
|
+
if @queue.size > MAX_QUEUE
|
|
58
|
+
@queue = @queue.last(MAX_QUEUE / 2)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# ── Flush ──────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
def flush_sync
|
|
66
|
+
loop do
|
|
67
|
+
batch = @mutex.synchronize { @queue.shift(BATCH_SIZE) }
|
|
68
|
+
break if batch.empty?
|
|
69
|
+
|
|
70
|
+
send_batch(batch)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def start_worker_thread!
|
|
77
|
+
@thread = Thread.new do
|
|
78
|
+
Thread.current.name = "lescopr-embedded" rescue nil
|
|
79
|
+
_connect_with_backoff
|
|
80
|
+
end
|
|
81
|
+
@thread.abort_on_exception = false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def _connect_with_backoff
|
|
85
|
+
delay = 2
|
|
86
|
+
attempts = 0
|
|
87
|
+
|
|
88
|
+
until @stop_event
|
|
89
|
+
sleep(FLUSH_INTERVAL) unless attempts.zero?
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
flush_sync
|
|
93
|
+
@connected = true
|
|
94
|
+
@logger.info("[EMBEDDED] ✅ Flush réussi (tentative #{attempts + 1})") if attempts > 0
|
|
95
|
+
attempts = 0
|
|
96
|
+
delay = 2
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
attempts += 1
|
|
99
|
+
@logger.debug("[EMBEDDED] Tentative #{attempts} échouée: #{e.message}")
|
|
100
|
+
|
|
101
|
+
if attempts >= MAX_CONNECT_ATTEMPTS
|
|
102
|
+
@logger.warn("[EMBEDDED] Trop d'échecs — pause de 60s avant retry")
|
|
103
|
+
sleep(60)
|
|
104
|
+
attempts = 0
|
|
105
|
+
delay = 2
|
|
106
|
+
else
|
|
107
|
+
sleep(delay)
|
|
108
|
+
delay = [delay * 2, 60].min
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def install_exit_hook!
|
|
115
|
+
at_exit { flush_sync }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def send_batch(batch)
|
|
119
|
+
return if batch.empty?
|
|
120
|
+
|
|
121
|
+
result = @http_client.ingest_logs(batch)
|
|
122
|
+
|
|
123
|
+
unless result
|
|
124
|
+
@mutex.synchronize do
|
|
125
|
+
@queue = batch + @queue
|
|
126
|
+
if @queue.size > MAX_QUEUE
|
|
127
|
+
@queue = @queue.last(MAX_QUEUE / 2)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
raise "ingest_logs returned nil"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
@logger.debug("[EMBEDDED] #{batch.size} log(s) envoyé(s)")
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -34,6 +34,18 @@ module Lescopr
|
|
|
34
34
|
!result.nil?
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Batch ingest via the new /logs/ingest endpoint (Direct / Embedded mode).
|
|
38
|
+
# Authenticated by X-SDK-Key header — no user session required.
|
|
39
|
+
#
|
|
40
|
+
# @param logs [Array<Hash>]
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def ingest_logs(logs)
|
|
43
|
+
return true if logs.empty?
|
|
44
|
+
|
|
45
|
+
result = post("/logs/ingest", { logs: logs })
|
|
46
|
+
!result.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
37
49
|
# Send a single heartbeat.
|
|
38
50
|
def send_heartbeat(sdk_id)
|
|
39
51
|
post("/sdk/heartbeat/", { sdk_id: sdk_id })
|
data/lib/lescopr/version.rb
CHANGED
data/lib/lescopr.rb
CHANGED
|
@@ -19,6 +19,11 @@ require_relative "lescopr/core/daemon_runner"
|
|
|
19
19
|
require_relative "lescopr/integrations/rack/middleware"
|
|
20
20
|
require_relative "lescopr/integrations/sinatra/extension"
|
|
21
21
|
|
|
22
|
+
# Modes package
|
|
23
|
+
require_relative "lescopr/modes/detector"
|
|
24
|
+
require_relative "lescopr/modes/direct"
|
|
25
|
+
require_relative "lescopr/modes/embedded"
|
|
26
|
+
|
|
22
27
|
# Rails Railtie is auto-loaded when Rails is present
|
|
23
28
|
if defined?(Rails)
|
|
24
29
|
require_relative "lescopr/integrations/rails/railtie"
|
|
@@ -56,6 +61,29 @@ module Lescopr
|
|
|
56
61
|
@client
|
|
57
62
|
end
|
|
58
63
|
|
|
64
|
+
# Zero-config init — loads SDK keys from .lescopr/config.json and
|
|
65
|
+
# auto-detects the best transport mode (daemon / embedded / direct).
|
|
66
|
+
#
|
|
67
|
+
# Usage (e.g. config/initializers/lescopr.rb or top of application.rb):
|
|
68
|
+
# Lescopr.logs
|
|
69
|
+
#
|
|
70
|
+
# @return [Lescopr::Core::Client, nil]
|
|
71
|
+
def logs
|
|
72
|
+
return @client if @client
|
|
73
|
+
|
|
74
|
+
config_mgr = Filesystem::ConfigManager.new
|
|
75
|
+
saved = config_mgr.load
|
|
76
|
+
return nil unless saved && saved[:sdk_key]
|
|
77
|
+
|
|
78
|
+
configuration.sdk_key = saved[:sdk_key]
|
|
79
|
+
configuration.api_key = saved[:api_key]
|
|
80
|
+
configuration.environment = saved[:environment] || "development"
|
|
81
|
+
|
|
82
|
+
@client = Core::Client.new(configuration)
|
|
83
|
+
@client.setup_auto_logging!
|
|
84
|
+
@client
|
|
85
|
+
end
|
|
86
|
+
|
|
59
87
|
# Shorthand initialiser — accepts a hash or keyword args.
|
|
60
88
|
#
|
|
61
89
|
# @param opts [Hash]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lescopr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SonnaLab
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -117,6 +117,9 @@ files:
|
|
|
117
117
|
- lib/lescopr/integrations/rack/middleware.rb
|
|
118
118
|
- lib/lescopr/integrations/rails/railtie.rb
|
|
119
119
|
- lib/lescopr/integrations/sinatra/extension.rb
|
|
120
|
+
- lib/lescopr/modes/detector.rb
|
|
121
|
+
- lib/lescopr/modes/direct.rb
|
|
122
|
+
- lib/lescopr/modes/embedded.rb
|
|
120
123
|
- lib/lescopr/monitoring/logger.rb
|
|
121
124
|
- lib/lescopr/transport/http_client.rb
|
|
122
125
|
- lib/lescopr/version.rb
|
|
@@ -130,7 +133,7 @@ metadata:
|
|
|
130
133
|
documentation_uri: https://docs.lescopr.com
|
|
131
134
|
bug_tracker_uri: https://github.com/Lescopr/lescopr-ruby/issues
|
|
132
135
|
rubygems_mfa_required: 'true'
|
|
133
|
-
post_install_message:
|
|
136
|
+
post_install_message:
|
|
134
137
|
rdoc_options: []
|
|
135
138
|
require_paths:
|
|
136
139
|
- lib
|
|
@@ -145,8 +148,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
145
148
|
- !ruby/object:Gem::Version
|
|
146
149
|
version: '0'
|
|
147
150
|
requirements: []
|
|
148
|
-
rubygems_version: 3.
|
|
149
|
-
signing_key:
|
|
151
|
+
rubygems_version: 3.0.3.1
|
|
152
|
+
signing_key:
|
|
150
153
|
specification_version: 4
|
|
151
154
|
summary: Zero-configuration Ruby monitoring SDK
|
|
152
155
|
test_files: []
|