mistral_translator 0.1.0 → 0.2.1
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 +21 -0
- data/README.md +189 -121
- data/README_TESTING.md +33 -0
- data/SECURITY.md +157 -0
- data/docs/.nojekyll +2 -0
- data/docs/404.html +30 -0
- data/docs/README.md +153 -0
- data/docs/advanced-usage/batch-processing.md +158 -0
- data/docs/advanced-usage/error-handling.md +106 -0
- data/docs/advanced-usage/monitoring.md +133 -0
- data/docs/advanced-usage/summarization.md +86 -0
- data/docs/advanced-usage/translations.md +141 -0
- data/docs/api-reference/callbacks.md +231 -0
- data/docs/api-reference/configuration.md +74 -0
- data/docs/api-reference/errors.md +673 -0
- data/docs/api-reference/methods.md +539 -0
- data/docs/getting-started.md +179 -0
- data/docs/index.html +27 -0
- data/docs/installation.md +142 -0
- data/docs/migration-0.1.0-to-0.2.0.md +61 -0
- data/docs/rails-integration/adapters.md +84 -0
- data/docs/rails-integration/controllers.md +107 -0
- data/docs/rails-integration/jobs.md +97 -0
- data/docs/rails-integration/setup.md +339 -0
- data/examples/basic_usage.rb +129 -102
- data/examples/batch-job.rb +511 -0
- data/examples/monitoring-setup.rb +499 -0
- data/examples/rails-model.rb +399 -0
- data/lib/mistral_translator/adapters.rb +261 -0
- data/lib/mistral_translator/client.rb +103 -100
- data/lib/mistral_translator/client_helpers.rb +161 -0
- data/lib/mistral_translator/configuration.rb +171 -1
- data/lib/mistral_translator/errors.rb +16 -0
- data/lib/mistral_translator/helpers.rb +292 -0
- data/lib/mistral_translator/helpers_extensions.rb +150 -0
- data/lib/mistral_translator/levenshtein_helpers.rb +40 -0
- data/lib/mistral_translator/logger.rb +28 -4
- data/lib/mistral_translator/prompt_builder.rb +93 -41
- data/lib/mistral_translator/prompt_helpers.rb +83 -0
- data/lib/mistral_translator/prompt_metadata_helpers.rb +42 -0
- data/lib/mistral_translator/response_parser.rb +194 -23
- data/lib/mistral_translator/security.rb +72 -0
- data/lib/mistral_translator/summarizer.rb +41 -2
- data/lib/mistral_translator/translator.rb +174 -98
- data/lib/mistral_translator/translator_helpers.rb +268 -0
- data/lib/mistral_translator/version.rb +1 -1
- data/lib/mistral_translator.rb +51 -25
- metadata +39 -3
@@ -0,0 +1,499 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Exemple de configuration complète de monitoring pour MistralTranslator
|
5
|
+
# Usage: ruby examples/monitoring-setup.rb
|
6
|
+
|
7
|
+
$stdout.sync = true
|
8
|
+
require "json"
|
9
|
+
require "fileutils"
|
10
|
+
require "time"
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
13
|
+
require "mistral_translator"
|
14
|
+
|
15
|
+
# === SETUP 1: Configuration de base avec métriques ===
|
16
|
+
|
17
|
+
puts "=== MistralTranslator Monitoring Setup ==="
|
18
|
+
|
19
|
+
# Configuration complète
|
20
|
+
MistralTranslator.configure do |config|
|
21
|
+
config.api_key = ENV["MISTRAL_API_KEY"] || "your_api_key_here"
|
22
|
+
config.enable_metrics = true
|
23
|
+
config.retry_delays = [1, 2, 4, 8, 16]
|
24
|
+
|
25
|
+
# Callbacks de monitoring
|
26
|
+
config.on_translation_start = lambda { |from, to, length, timestamp|
|
27
|
+
puts "🚀 [#{timestamp}] Translation #{from}→#{to} starting (#{length} chars)"
|
28
|
+
}
|
29
|
+
|
30
|
+
config.on_translation_complete = lambda { |from, to, orig_len, trans_len, duration|
|
31
|
+
puts "✅ Translation #{from}→#{to} completed in #{duration.round(3)}s"
|
32
|
+
puts " Efficiency: #{(trans_len.to_f / orig_len).round(2)} chars/char"
|
33
|
+
}
|
34
|
+
|
35
|
+
config.on_translation_error = lambda { |from, to, error, attempt, timestamp|
|
36
|
+
puts "❌ [#{timestamp}] Translation #{from}→#{to} failed (attempt #{attempt}): #{error.message}"
|
37
|
+
}
|
38
|
+
|
39
|
+
config.on_rate_limit = lambda { |from, to, wait_time, attempt, timestamp|
|
40
|
+
puts "⏳ [#{timestamp}] Rate limit #{from}→#{to}, waiting #{wait_time}s (attempt #{attempt})"
|
41
|
+
}
|
42
|
+
|
43
|
+
config.on_batch_complete = lambda { |size, duration, success, errors|
|
44
|
+
puts "📦 Batch completed: #{success}/#{size} success in #{duration.round(2)}s (#{errors} errors)"
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# === CLASSE: Collecteur de métriques avancé ===
|
49
|
+
|
50
|
+
class TranslationMetricsCollector
|
51
|
+
CallbackSet = Struct.new(:start, :complete, :error, :rate_limit)
|
52
|
+
def initialize
|
53
|
+
@stats = {
|
54
|
+
translations: 0,
|
55
|
+
errors: 0,
|
56
|
+
rate_limits: 0,
|
57
|
+
total_duration: 0.0,
|
58
|
+
total_chars_input: 0,
|
59
|
+
total_chars_output: 0,
|
60
|
+
by_language: Hash.new(0),
|
61
|
+
error_types: Hash.new(0),
|
62
|
+
hourly_stats: Hash.new { |h, k| h[k] = { count: 0, errors: 0 } }
|
63
|
+
}
|
64
|
+
@start_time = Time.now
|
65
|
+
setup_callbacks!
|
66
|
+
end
|
67
|
+
|
68
|
+
def setup_callbacks!
|
69
|
+
originals = fetch_original_callbacks
|
70
|
+
|
71
|
+
MistralTranslator.configure do |config|
|
72
|
+
assign_wrapped_callbacks(config, originals)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fetch_original_callbacks
|
77
|
+
config = MistralTranslator.configuration
|
78
|
+
CallbackSet.new(
|
79
|
+
config.on_translation_start,
|
80
|
+
config.on_translation_complete,
|
81
|
+
config.on_translation_error,
|
82
|
+
config.on_rate_limit
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def assign_wrapped_callbacks(config, originals)
|
87
|
+
config.on_translation_start = lambda { |from, to, length, timestamp|
|
88
|
+
originals.start&.call(from, to, length, timestamp)
|
89
|
+
on_translation_start(from, to, length, timestamp)
|
90
|
+
}
|
91
|
+
|
92
|
+
config.on_translation_complete = lambda { |from, to, orig_len, trans_len, duration|
|
93
|
+
originals.complete&.call(from, to, orig_len, trans_len, duration)
|
94
|
+
on_translation_complete(from, to, orig_len, trans_len, duration)
|
95
|
+
}
|
96
|
+
|
97
|
+
config.on_translation_error = lambda { |from, to, error, attempt, timestamp|
|
98
|
+
originals.error&.call(from, to, error, attempt, timestamp)
|
99
|
+
on_translation_error(from, to, error, attempt, timestamp)
|
100
|
+
}
|
101
|
+
|
102
|
+
config.on_rate_limit = lambda { |from, to, wait_time, attempt, timestamp|
|
103
|
+
originals.rate_limit&.call(from, to, wait_time, attempt, timestamp)
|
104
|
+
on_rate_limit(from, to, wait_time, attempt, timestamp)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def on_translation_start(_from, _to, length, timestamp)
|
109
|
+
hour_key = timestamp.strftime("%Y-%m-%d %H:00")
|
110
|
+
@stats[:hourly_stats][hour_key][:count] += 1
|
111
|
+
@stats[:total_chars_input] += length
|
112
|
+
end
|
113
|
+
|
114
|
+
def on_translation_complete(from, to, _orig_len, trans_len, duration)
|
115
|
+
@stats[:translations] += 1
|
116
|
+
@stats[:total_duration] += duration
|
117
|
+
@stats[:total_chars_output] += trans_len
|
118
|
+
@stats[:by_language]["#{from}_to_#{to}"] += 1
|
119
|
+
end
|
120
|
+
|
121
|
+
def on_translation_error(_from, _to, error, _attempt, timestamp)
|
122
|
+
@stats[:errors] += 1
|
123
|
+
@stats[:error_types][error.class.name] += 1
|
124
|
+
|
125
|
+
hour_key = timestamp.strftime("%Y-%m-%d %H:00")
|
126
|
+
@stats[:hourly_stats][hour_key][:errors] += 1
|
127
|
+
end
|
128
|
+
|
129
|
+
def on_rate_limit(_from, _to, _wait_time, _attempt, _timestamp)
|
130
|
+
@stats[:rate_limits] += 1
|
131
|
+
end
|
132
|
+
|
133
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
134
|
+
def report
|
135
|
+
uptime = Time.now - @start_time
|
136
|
+
|
137
|
+
puts "\n#{"=" * 50}"
|
138
|
+
puts "📊 TRANSLATION METRICS REPORT"
|
139
|
+
puts "=" * 50
|
140
|
+
|
141
|
+
# Statistiques globales
|
142
|
+
puts "⏱️ Uptime: #{uptime.round(2)}s"
|
143
|
+
puts "📝 Translations: #{@stats[:translations]}"
|
144
|
+
puts "❌ Errors: #{@stats[:errors]} (#{error_rate}%)"
|
145
|
+
puts "⏳ Rate limits: #{@stats[:rate_limits]}"
|
146
|
+
|
147
|
+
# Performance
|
148
|
+
if @stats[:translations].positive?
|
149
|
+
avg_duration = @stats[:total_duration] / @stats[:translations]
|
150
|
+
puts "⚡ Avg duration: #{avg_duration.round(3)}s per translation"
|
151
|
+
puts "📊 Throughput: #{(@stats[:translations] / uptime).round(2)} translations/sec"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Caractères
|
155
|
+
puts "📄 Input chars: #{@stats[:total_chars_input]}"
|
156
|
+
puts "📄 Output chars: #{@stats[:total_chars_output]}"
|
157
|
+
if @stats[:total_chars_input].positive?
|
158
|
+
expansion = @stats[:total_chars_output].to_f / @stats[:total_chars_input]
|
159
|
+
puts "📈 Text expansion: #{expansion.round(2)}x"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Top langues
|
163
|
+
if @stats[:by_language].any?
|
164
|
+
puts "\n🌐 Top language pairs:"
|
165
|
+
@stats[:by_language].sort_by { |_, count| -count }.first(5).each do |pair, count|
|
166
|
+
percentage = (count.to_f / @stats[:translations] * 100).round(1)
|
167
|
+
puts " #{pair.gsub("_to_", " → ")}: #{count} (#{percentage}%)"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Top erreurs
|
172
|
+
if @stats[:error_types].any?
|
173
|
+
puts "\n❌ Error breakdown:"
|
174
|
+
@stats[:error_types].each do |error_type, count|
|
175
|
+
percentage = (count.to_f / @stats[:errors] * 100).round(1)
|
176
|
+
puts " #{error_type}: #{count} (#{percentage}%)"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Statistiques horaires
|
181
|
+
if @stats[:hourly_stats].any?
|
182
|
+
puts "\n🕐 Activity by hour (last 5):"
|
183
|
+
@stats[:hourly_stats].sort_by { |hour, _| hour }.last(5).each do |hour, stats|
|
184
|
+
error_rate = stats[:count].positive? ? (stats[:errors].to_f / stats[:count] * 100).round(1) : 0
|
185
|
+
puts " #{hour}: #{stats[:count]} translations, #{stats[:errors]} errors (#{error_rate}%)"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
puts "=" * 50
|
190
|
+
end
|
191
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
192
|
+
|
193
|
+
def error_rate
|
194
|
+
return 0 if @stats[:translations].zero?
|
195
|
+
|
196
|
+
(@stats[:errors].to_f / (@stats[:translations] + @stats[:errors]) * 100).round(1)
|
197
|
+
end
|
198
|
+
|
199
|
+
def health_status
|
200
|
+
if error_rate > 20
|
201
|
+
{ status: :critical, message: "High error rate: #{error_rate}%" }
|
202
|
+
elsif error_rate > 10
|
203
|
+
{ status: :warning, message: "Elevated error rate: #{error_rate}%" }
|
204
|
+
elsif @stats[:rate_limits] > @stats[:translations] * 0.5
|
205
|
+
{ status: :warning, message: "Frequent rate limiting" }
|
206
|
+
else
|
207
|
+
{ status: :healthy, message: "All systems operational" }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# === CLASSE: Alerting System ===
|
213
|
+
|
214
|
+
class AlertingSystem
|
215
|
+
def initialize(metrics_collector)
|
216
|
+
@metrics = metrics_collector
|
217
|
+
@alerts_sent = Set.new
|
218
|
+
@alert_cooldown = 300 # 5 minutes
|
219
|
+
@last_alerts = {}
|
220
|
+
end
|
221
|
+
|
222
|
+
def check_and_alert!
|
223
|
+
health = @metrics.health_status
|
224
|
+
|
225
|
+
case health[:status]
|
226
|
+
when :critical
|
227
|
+
send_alert(:critical, health[:message])
|
228
|
+
when :warning
|
229
|
+
send_alert(:warning, health[:message])
|
230
|
+
end
|
231
|
+
|
232
|
+
# Vérifications spécifiques
|
233
|
+
check_rate_limiting!
|
234
|
+
check_error_spikes!
|
235
|
+
check_performance_degradation!
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def check_rate_limiting!
|
241
|
+
return unless @metrics.instance_variable_get(:@stats)[:rate_limits] > 10
|
242
|
+
|
243
|
+
send_alert(:warning, "Excessive rate limiting detected")
|
244
|
+
end
|
245
|
+
|
246
|
+
def check_error_spikes!
|
247
|
+
return unless @metrics.error_rate > 15
|
248
|
+
|
249
|
+
send_alert(:critical, "Error rate spike: #{@metrics.error_rate}%")
|
250
|
+
end
|
251
|
+
|
252
|
+
def check_performance_degradation!
|
253
|
+
stats = @metrics.instance_variable_get(:@stats)
|
254
|
+
return unless stats[:translations].positive?
|
255
|
+
|
256
|
+
avg_duration = stats[:total_duration] / stats[:translations]
|
257
|
+
return unless avg_duration > 10.0 # Plus de 10 secondes en moyenne
|
258
|
+
|
259
|
+
send_alert(:warning, "Performance degradation: #{avg_duration.round(2)}s avg")
|
260
|
+
end
|
261
|
+
|
262
|
+
def send_alert(level, message)
|
263
|
+
alert_key = "#{level}_#{message}"
|
264
|
+
|
265
|
+
# Cooldown check
|
266
|
+
return if @last_alerts[alert_key] && Time.now - @last_alerts[alert_key] < @alert_cooldown
|
267
|
+
|
268
|
+
@last_alerts[alert_key] = Time.now
|
269
|
+
|
270
|
+
# Simuler l'envoi d'alerte
|
271
|
+
icon = level == :critical ? "🚨" : "⚠️"
|
272
|
+
puts "#{icon} ALERT [#{level.upcase}]: #{message}"
|
273
|
+
|
274
|
+
# Ici vous pourriez intégrer:
|
275
|
+
# - Slack notifications
|
276
|
+
# - Email alerts
|
277
|
+
# - PagerDuty
|
278
|
+
# - Sentry
|
279
|
+
# - Custom webhooks
|
280
|
+
|
281
|
+
simulate_external_alert(level, message)
|
282
|
+
end
|
283
|
+
|
284
|
+
def simulate_external_alert(_level, _message)
|
285
|
+
# Exemple d'intégration Slack
|
286
|
+
if ENV["SLACK_WEBHOOK_URL"]
|
287
|
+
# webhook_payload = {
|
288
|
+
# text: "MistralTranslator Alert",
|
289
|
+
# attachments: [{
|
290
|
+
# color: level == :critical ? "danger" : "warning",
|
291
|
+
# fields: [{
|
292
|
+
# title: level.to_s.capitalize,
|
293
|
+
# value: message,
|
294
|
+
# short: false
|
295
|
+
# }]
|
296
|
+
# }]
|
297
|
+
# }
|
298
|
+
puts " → Slack notification sent"
|
299
|
+
end
|
300
|
+
|
301
|
+
# Exemple d'intégration Sentry
|
302
|
+
return unless ENV["SENTRY_DSN"]
|
303
|
+
|
304
|
+
# Sentry.capture_message(message, level: level)
|
305
|
+
puts " → Sentry event created"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# === CLASSE: Dashboard Data Generator ===
|
310
|
+
|
311
|
+
class MonitoringDashboard
|
312
|
+
def initialize(metrics_collector)
|
313
|
+
@metrics = metrics_collector
|
314
|
+
end
|
315
|
+
|
316
|
+
def generate_dashboard_data
|
317
|
+
stats = @metrics.instance_variable_get(:@stats)
|
318
|
+
|
319
|
+
{
|
320
|
+
overview: {
|
321
|
+
translations_count: stats[:translations],
|
322
|
+
error_count: stats[:errors],
|
323
|
+
error_rate: @metrics.error_rate,
|
324
|
+
rate_limits: stats[:rate_limits],
|
325
|
+
health_status: @metrics.health_status
|
326
|
+
},
|
327
|
+
|
328
|
+
performance: {
|
329
|
+
avg_duration: stats[:translations].positive? ? (stats[:total_duration] / stats[:translations]).round(3) : 0,
|
330
|
+
total_duration: stats[:total_duration].round(3),
|
331
|
+
chars_per_second: calculate_chars_per_second(stats),
|
332
|
+
text_expansion_ratio: calculate_expansion_ratio(stats)
|
333
|
+
},
|
334
|
+
|
335
|
+
usage: {
|
336
|
+
language_pairs: stats[:by_language].sort_by { |_, count| -count }.first(10),
|
337
|
+
hourly_activity: stats[:hourly_stats].sort_by { |hour, _| hour }.last(24),
|
338
|
+
error_breakdown: stats[:error_types]
|
339
|
+
},
|
340
|
+
|
341
|
+
system: {
|
342
|
+
uptime: (Time.now - @metrics.instance_variable_get(:@start_time)).round(2),
|
343
|
+
gem_version: MistralTranslator::VERSION,
|
344
|
+
ruby_version: RUBY_VERSION,
|
345
|
+
timestamp: Time.now.iso8601
|
346
|
+
}
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
def export_to_json(filename = "translation_dashboard_#{Time.now.strftime("%Y%m%d_%H%M%S")}.json")
|
351
|
+
data = generate_dashboard_data
|
352
|
+
File.write(filename, JSON.pretty_generate(data))
|
353
|
+
puts "📄 Dashboard data exported to: #{filename}"
|
354
|
+
filename
|
355
|
+
end
|
356
|
+
|
357
|
+
private
|
358
|
+
|
359
|
+
def calculate_chars_per_second(stats)
|
360
|
+
uptime = Time.now - @metrics.instance_variable_get(:@start_time)
|
361
|
+
uptime.positive? ? (stats[:total_chars_input] / uptime).round(2) : 0
|
362
|
+
end
|
363
|
+
|
364
|
+
def calculate_expansion_ratio(stats)
|
365
|
+
return 1.0 if stats[:total_chars_input].zero?
|
366
|
+
|
367
|
+
(stats[:total_chars_output].to_f / stats[:total_chars_input]).round(3)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# === UTILISATION ET DÉMONSTRATION ===
|
372
|
+
|
373
|
+
puts "\n1. Setting up monitoring..."
|
374
|
+
metrics = TranslationMetricsCollector.new
|
375
|
+
alerting = AlertingSystem.new(metrics)
|
376
|
+
dashboard = MonitoringDashboard.new(metrics)
|
377
|
+
|
378
|
+
puts "\n2. Testing with sample translations..."
|
379
|
+
|
380
|
+
# Test basique pour vérifier la connexion
|
381
|
+
begin
|
382
|
+
test_result = MistralTranslator.translate("Hello monitoring", from: "en", to: "fr")
|
383
|
+
puts "API connection OK: #{test_result}"
|
384
|
+
rescue MistralTranslator::Error => e
|
385
|
+
puts "API Error: #{e.message}"
|
386
|
+
puts "Continuing with monitoring setup demo..."
|
387
|
+
end
|
388
|
+
|
389
|
+
# Simuler différents scénarios
|
390
|
+
sample_texts = [
|
391
|
+
"Bonjour le monde",
|
392
|
+
"Comment allez-vous ?",
|
393
|
+
"Ruby on Rails",
|
394
|
+
"Test de monitoring",
|
395
|
+
"Système de surveillance"
|
396
|
+
]
|
397
|
+
|
398
|
+
puts "\n3. Running sample translations to generate metrics..."
|
399
|
+
|
400
|
+
sample_texts.each_with_index do |text, i|
|
401
|
+
MistralTranslator.translate(text, from: "fr", to: "en")
|
402
|
+
print "."
|
403
|
+
|
404
|
+
# Simuler quelques erreurs pour les métriques
|
405
|
+
if i == 2
|
406
|
+
begin
|
407
|
+
MistralTranslator.translate("", from: "invalid", to: "fr")
|
408
|
+
rescue MistralTranslator::Error
|
409
|
+
# Erreur attendue pour les métriques
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
sleep(0.2) # Rate limiting
|
414
|
+
rescue MistralTranslator::Error => e
|
415
|
+
print "x"
|
416
|
+
end
|
417
|
+
|
418
|
+
puts "\n\n4. Checking alerts..."
|
419
|
+
alerting.check_and_alert!
|
420
|
+
|
421
|
+
puts "\n5. Generating reports..."
|
422
|
+
metrics.report
|
423
|
+
|
424
|
+
puts "\n6. Health status check..."
|
425
|
+
health = metrics.health_status
|
426
|
+
puts "🏥 Health: #{health[:status]} - #{health[:message]}"
|
427
|
+
|
428
|
+
puts "\n7. Exporting dashboard data..."
|
429
|
+
dashboard_file = dashboard.export_to_json
|
430
|
+
|
431
|
+
puts "\n8. Built-in metrics from MistralTranslator:"
|
432
|
+
if MistralTranslator.configuration.enable_metrics
|
433
|
+
built_in_metrics = MistralTranslator.metrics
|
434
|
+
puts "📊 Built-in metrics:"
|
435
|
+
built_in_metrics.each do |key, value|
|
436
|
+
puts " #{key}: #{value}"
|
437
|
+
end
|
438
|
+
else
|
439
|
+
puts "⚠️ Built-in metrics not enabled"
|
440
|
+
end
|
441
|
+
|
442
|
+
# === CONFIGURATION POUR PRODUCTION ===
|
443
|
+
|
444
|
+
puts "\n#{"=" * 50}"
|
445
|
+
puts "📋 PRODUCTION MONITORING SETUP EXAMPLE"
|
446
|
+
puts "=" * 50
|
447
|
+
|
448
|
+
puts <<~SETUP
|
449
|
+
# config/initializers/mistral_translator.rb
|
450
|
+
MistralTranslator.configure do |config|
|
451
|
+
config.api_key = ENV['MISTRAL_API_KEY']
|
452
|
+
config.enable_metrics = true
|
453
|
+
#{" "}
|
454
|
+
# Callbacks pour intégration monitoring
|
455
|
+
config.on_translation_error = ->(from, to, error, attempt, timestamp) {
|
456
|
+
# Sentry/Honeybadger
|
457
|
+
if defined?(Sentry)
|
458
|
+
Sentry.capture_exception(error, extra: {
|
459
|
+
translation_direction: "\#{from} -> \#{to}",
|
460
|
+
attempt_number: attempt
|
461
|
+
})
|
462
|
+
end
|
463
|
+
#{" "}
|
464
|
+
# Logs structurés
|
465
|
+
Rails.logger.error({
|
466
|
+
event: 'translation_error',
|
467
|
+
from_language: from,
|
468
|
+
to_language: to,
|
469
|
+
error_class: error.class.name,
|
470
|
+
error_message: error.message,
|
471
|
+
attempt: attempt,
|
472
|
+
timestamp: timestamp
|
473
|
+
}.to_json)
|
474
|
+
}
|
475
|
+
#{" "}
|
476
|
+
config.on_rate_limit = ->(from, to, wait_time, attempt, timestamp) {
|
477
|
+
# Métriques custom (StatsD, DataDog, etc.)
|
478
|
+
if defined?(StatsD)
|
479
|
+
StatsD.increment('mistral_translator.rate_limit',
|
480
|
+
tags: ["from:\#{from}", "to:\#{to}"])
|
481
|
+
end
|
482
|
+
}
|
483
|
+
#{" "}
|
484
|
+
config.on_translation_complete = ->(from, to, orig, trans, duration) {
|
485
|
+
# Métriques de performance
|
486
|
+
if defined?(StatsD)
|
487
|
+
StatsD.timing('mistral_translator.duration', duration * 1000,
|
488
|
+
tags: ["from:\#{from}", "to:\#{to}"])
|
489
|
+
StatsD.histogram('mistral_translator.chars_ratio',#{" "}
|
490
|
+
trans.to_f / orig, tags: ["from:\#{from}", "to:\#{to}"])
|
491
|
+
end
|
492
|
+
}
|
493
|
+
end
|
494
|
+
SETUP
|
495
|
+
|
496
|
+
puts "\n🎉 Monitoring setup complete!"
|
497
|
+
|
498
|
+
# Nettoyage
|
499
|
+
FileUtils.rm_f(dashboard_file)
|