ez_logs_agent 0.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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +57 -0
- data/CONFIGURATION.md +752 -0
- data/FAQ.md +574 -0
- data/LICENSE.txt +21 -0
- data/QUICKSTART.md +390 -0
- data/README.md +1021 -0
- data/RELEASING.md +55 -0
- data/Rakefile +8 -0
- data/lib/ez_logs_agent/actor.rb +57 -0
- data/lib/ez_logs_agent/actor_validator.rb +51 -0
- data/lib/ez_logs_agent/buffer.rb +83 -0
- data/lib/ez_logs_agent/capturers/active_job_capturer.rb +270 -0
- data/lib/ez_logs_agent/capturers/database_capturer.rb +467 -0
- data/lib/ez_logs_agent/capturers/job_capturer.rb +238 -0
- data/lib/ez_logs_agent/configuration.rb +186 -0
- data/lib/ez_logs_agent/configuration_validator.rb +139 -0
- data/lib/ez_logs_agent/correlation.rb +40 -0
- data/lib/ez_logs_agent/event_builder.rb +281 -0
- data/lib/ez_logs_agent/flush_scheduler.rb +99 -0
- data/lib/ez_logs_agent/logger.rb +62 -0
- data/lib/ez_logs_agent/middleware/http_request.rb +1094 -0
- data/lib/ez_logs_agent/railtie.rb +353 -0
- data/lib/ez_logs_agent/resource_extractor.rb +172 -0
- data/lib/ez_logs_agent/retry_sender.rb +120 -0
- data/lib/ez_logs_agent/transport.rb +91 -0
- data/lib/ez_logs_agent/version.rb +5 -0
- data/lib/ez_logs_agent.rb +42 -0
- data/lib/generators/ez_logs_agent/install/install_generator.rb +94 -0
- data/lib/generators/ez_logs_agent/install/templates/ez_logs_agent.rb.tt +128 -0
- data/lib/tasks/ez_logs_agent.rake +110 -0
- data/script/publish-to-public.sh +113 -0
- data/sig/ez_logs_agent.rbs +4 -0
- metadata +178 -0
data/RELEASING.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Release Checklist
|
|
2
|
+
|
|
3
|
+
Steps to release a new version of `ez_logs_agent`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Pre-Release
|
|
8
|
+
|
|
9
|
+
- [ ] All tests passing: `bundle exec rspec`
|
|
10
|
+
- [ ] Code style clean: `bundle exec rubocop` (if configured)
|
|
11
|
+
- [ ] README.md reviewed and accurate
|
|
12
|
+
- [ ] CHANGELOG.md updated with new version
|
|
13
|
+
- [ ] Version bumped in `lib/ez_logs_agent/version.rb`
|
|
14
|
+
- [ ] Gemspec metadata reviewed (summary, description, homepage)
|
|
15
|
+
- [ ] No uncommitted changes: `git status`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Release
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Build the gem
|
|
23
|
+
gem build ez_logs_agent.gemspec
|
|
24
|
+
|
|
25
|
+
# Verify the gem contents
|
|
26
|
+
gem specification ez_logs_agent-X.Y.Z.gem
|
|
27
|
+
|
|
28
|
+
# Push to RubyGems (requires authentication)
|
|
29
|
+
gem push ez_logs_agent-X.Y.Z.gem
|
|
30
|
+
|
|
31
|
+
# Tag the release
|
|
32
|
+
git tag vX.Y.Z
|
|
33
|
+
git push origin vX.Y.Z
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Post-Release
|
|
39
|
+
|
|
40
|
+
- [ ] Verify gem is available: `gem info ez_logs_agent -r`
|
|
41
|
+
- [ ] Create GitHub release with changelog notes
|
|
42
|
+
- [ ] Monitor for issues or bug reports
|
|
43
|
+
- [ ] Update CURRENT_STATE.md if needed
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Version Numbering
|
|
48
|
+
|
|
49
|
+
This project follows [Semantic Versioning](https://semver.org/):
|
|
50
|
+
|
|
51
|
+
- **MAJOR** (X.0.0): Breaking changes to public API
|
|
52
|
+
- **MINOR** (0.X.0): New features, backwards compatible
|
|
53
|
+
- **PATCH** (0.0.X): Bug fixes, backwards compatible
|
|
54
|
+
|
|
55
|
+
Pre-1.0: API is not yet stable. Minor version bumps may include breaking changes.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "request_store"
|
|
4
|
+
|
|
5
|
+
module EzLogsAgent
|
|
6
|
+
# Manages actor context for the current request.
|
|
7
|
+
#
|
|
8
|
+
# Actor represents "who triggered this action" - the logged-in user.
|
|
9
|
+
#
|
|
10
|
+
# Actor schema:
|
|
11
|
+
# {
|
|
12
|
+
# id: String, # REQUIRED - stable identifier (e.g., user.id)
|
|
13
|
+
# label: String # optional - human-readable display (e.g., user.email)
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# # Configured via actor_from_request hook in EzLogsAgent.configure
|
|
18
|
+
module Actor
|
|
19
|
+
class << self
|
|
20
|
+
# Get the current actor for this request
|
|
21
|
+
# @return [Hash, nil] Current actor or nil if not set
|
|
22
|
+
def current
|
|
23
|
+
RequestStore.store[:ez_logs_actor]
|
|
24
|
+
rescue => e
|
|
25
|
+
EzLogsAgent::Logger.debug("[Actor] current failed: #{e.message}")
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Set the current actor for this request
|
|
30
|
+
# Validates and sanitizes the actor before storing
|
|
31
|
+
# @param actor [Hash, nil] Actor to set
|
|
32
|
+
# @return [Hash, nil] The sanitized actor that was stored
|
|
33
|
+
def current=(actor)
|
|
34
|
+
validated = ActorValidator.sanitize(actor)
|
|
35
|
+
|
|
36
|
+
# Log debug message if actor was invalid (user hook misconfiguration)
|
|
37
|
+
if actor && validated.nil?
|
|
38
|
+
EzLogsAgent::Logger.debug("[Actor] Invalid actor structure ignored: #{actor.inspect}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
RequestStore.store[:ez_logs_actor] = validated
|
|
42
|
+
validated
|
|
43
|
+
rescue => e
|
|
44
|
+
EzLogsAgent::Logger.debug("[Actor] current= failed: #{e.message}")
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Clear the current actor
|
|
49
|
+
# @return [void]
|
|
50
|
+
def clear
|
|
51
|
+
RequestStore.store[:ez_logs_actor] = nil
|
|
52
|
+
rescue => e
|
|
53
|
+
EzLogsAgent::Logger.debug("[Actor] clear failed: #{e.message}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EzLogsAgent
|
|
4
|
+
# Validates and sanitizes actor data structures.
|
|
5
|
+
#
|
|
6
|
+
# Actor schema:
|
|
7
|
+
# {
|
|
8
|
+
# id: String, # REQUIRED, stable identifier
|
|
9
|
+
# label: String | nil # optional, human-readable display
|
|
10
|
+
# }
|
|
11
|
+
#
|
|
12
|
+
# This module ensures actors conform to the expected structure
|
|
13
|
+
# before being stored in event context.
|
|
14
|
+
module ActorValidator
|
|
15
|
+
class << self
|
|
16
|
+
# Check if an actor structure is valid
|
|
17
|
+
# @param actor [Hash, nil] Actor hash to validate
|
|
18
|
+
# @return [Boolean] true if valid (including nil), false otherwise
|
|
19
|
+
def valid?(actor)
|
|
20
|
+
# nil actor is valid (means "unknown provenance")
|
|
21
|
+
return true if actor.nil?
|
|
22
|
+
|
|
23
|
+
# Must be a Hash
|
|
24
|
+
return false unless actor.is_a?(Hash)
|
|
25
|
+
|
|
26
|
+
# id is required
|
|
27
|
+
id = actor[:id] || actor["id"]
|
|
28
|
+
return false if id.nil? || id.to_s.empty?
|
|
29
|
+
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Sanitize actor structure to ensure consistent format
|
|
34
|
+
# Returns nil for invalid actors
|
|
35
|
+
# @param actor [Hash, nil] Actor hash to sanitize
|
|
36
|
+
# @return [Hash, nil] Sanitized actor or nil
|
|
37
|
+
def sanitize(actor)
|
|
38
|
+
return nil unless valid?(actor)
|
|
39
|
+
return nil if actor.nil?
|
|
40
|
+
|
|
41
|
+
id = actor[:id] || actor["id"]
|
|
42
|
+
label = actor[:label] || actor["label"]
|
|
43
|
+
|
|
44
|
+
result = { id: id.to_s }
|
|
45
|
+
result[:label] = label.to_s if label
|
|
46
|
+
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'monitor'
|
|
4
|
+
|
|
5
|
+
module EzLogsAgent
|
|
6
|
+
# Thread-safe in-memory event buffer.
|
|
7
|
+
# Stores events until flushed. Drops oldest when full.
|
|
8
|
+
# Never raises exceptions to host application.
|
|
9
|
+
class Buffer
|
|
10
|
+
@queue = []
|
|
11
|
+
@monitor = Monitor.new
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Add event to buffer. Drops oldest if full.
|
|
15
|
+
# @param event [Hash] Event hash from EventBuilder
|
|
16
|
+
# @return [void]
|
|
17
|
+
def push(event)
|
|
18
|
+
@monitor.synchronize do
|
|
19
|
+
if @queue.size >= max_size
|
|
20
|
+
@queue.shift
|
|
21
|
+
log_warn("[Buffer] Buffer full (#{max_size}), dropped oldest event")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@queue.push(event)
|
|
25
|
+
end
|
|
26
|
+
rescue => error
|
|
27
|
+
log_error("[Buffer] push failed: #{error.message}")
|
|
28
|
+
# Fail open: discard event, never crash host
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Flush all buffered events and clear buffer atomically.
|
|
32
|
+
# @return [Array<Hash>] Array of events (empty if buffer was empty)
|
|
33
|
+
def flush
|
|
34
|
+
@monitor.synchronize do
|
|
35
|
+
events = @queue.dup
|
|
36
|
+
@queue.clear
|
|
37
|
+
events
|
|
38
|
+
end
|
|
39
|
+
rescue => error
|
|
40
|
+
log_error("[Buffer] flush failed: #{error.message}")
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Current number of buffered events.
|
|
45
|
+
# @return [Integer]
|
|
46
|
+
def size
|
|
47
|
+
@monitor.synchronize { @queue.size }
|
|
48
|
+
rescue => error
|
|
49
|
+
log_error("[Buffer] size failed: #{error.message}")
|
|
50
|
+
0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Clear all buffered events.
|
|
54
|
+
# @return [void]
|
|
55
|
+
def clear
|
|
56
|
+
@monitor.synchronize { @queue.clear }
|
|
57
|
+
rescue => error
|
|
58
|
+
log_error("[Buffer] clear failed: #{error.message}")
|
|
59
|
+
# Best effort, ignore failures
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def max_size
|
|
65
|
+
EzLogsAgent.configuration.buffer_size
|
|
66
|
+
rescue
|
|
67
|
+
100 # Defensive fallback if configuration unavailable
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def log_warn(message)
|
|
71
|
+
EzLogsAgent::Logger.warn(message) if defined?(EzLogsAgent::Logger)
|
|
72
|
+
rescue
|
|
73
|
+
# Logging must never crash the buffer
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def log_error(message)
|
|
77
|
+
EzLogsAgent::Logger.error(message) if defined?(EzLogsAgent::Logger)
|
|
78
|
+
rescue
|
|
79
|
+
# Logging must never crash the buffer
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EzLogsAgent
|
|
4
|
+
module Capturers
|
|
5
|
+
# ActiveJob hooks for correlation propagation and job execution capture.
|
|
6
|
+
#
|
|
7
|
+
# This capturer provides first-class ActiveJob support with two responsibilities:
|
|
8
|
+
#
|
|
9
|
+
# 1. **Enqueue-Time (Correlation Propagation)**:
|
|
10
|
+
# - Uses `before_enqueue` callback to inject correlation_id into job
|
|
11
|
+
# - Preserves causal chain: HTTP → Job → Job
|
|
12
|
+
#
|
|
13
|
+
# 2. **Execution-Time (Job Capture)**:
|
|
14
|
+
# - Uses `around_perform` callback to capture job execution as background_job events
|
|
15
|
+
# - Restores correlation_id from job
|
|
16
|
+
# - Measures duration and captures success/failure outcome
|
|
17
|
+
#
|
|
18
|
+
# == Serialization Support
|
|
19
|
+
#
|
|
20
|
+
# ActiveJob's metadata hash is NOT automatically serialized. This capturer
|
|
21
|
+
# overrides `serialize` and `deserialize` to persist the correlation_id
|
|
22
|
+
# across job serialization (required for Async adapter and other adapters
|
|
23
|
+
# that serialize jobs).
|
|
24
|
+
#
|
|
25
|
+
# == Sidekiq Adapter Detection
|
|
26
|
+
#
|
|
27
|
+
# If the job's queue adapter is Sidekiq, this capturer:
|
|
28
|
+
# - STILL propagates correlation at enqueue-time
|
|
29
|
+
# - SKIPS execution capture (defers to Sidekiq server middleware)
|
|
30
|
+
#
|
|
31
|
+
# This prevents double events when Sidekiq is the adapter.
|
|
32
|
+
#
|
|
33
|
+
# == Installation
|
|
34
|
+
#
|
|
35
|
+
# This capturer is automatically installed by Railtie when ActiveJob
|
|
36
|
+
# is detected and `capture_jobs = true`.
|
|
37
|
+
#
|
|
38
|
+
# For manual installation (if not using Rails):
|
|
39
|
+
#
|
|
40
|
+
# EzLogsAgent::Capturers::ActiveJobCapturer.install
|
|
41
|
+
#
|
|
42
|
+
class ActiveJobCapturer
|
|
43
|
+
@serialization_installed = false
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
# Installs ActiveJob hooks for correlation propagation and job capture.
|
|
47
|
+
#
|
|
48
|
+
# This method is idempotent and can be called multiple times safely.
|
|
49
|
+
#
|
|
50
|
+
# @return [void]
|
|
51
|
+
def install
|
|
52
|
+
return unless defined?(ActiveJob)
|
|
53
|
+
|
|
54
|
+
install_serialization_hooks unless @serialization_installed
|
|
55
|
+
|
|
56
|
+
ActiveJob::Base.before_enqueue do |job|
|
|
57
|
+
ActiveJobCapturer.propagate_correlation(job)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ActiveJob::Base.around_perform do |job, block|
|
|
61
|
+
ActiveJobCapturer.capture_execution(job, block)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
EzLogsAgent::Logger.debug("[ActiveJobCapturer] Hooks installed")
|
|
65
|
+
rescue StandardError => e
|
|
66
|
+
EzLogsAgent::Logger.error("[ActiveJobCapturer] Installation failed: #{e.class} - #{e.message}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Propagates correlation_id from current context into job.
|
|
70
|
+
#
|
|
71
|
+
# Runs at enqueue-time (when job is scheduled).
|
|
72
|
+
# Stores correlation in `ezlogs_correlation_id` attribute which
|
|
73
|
+
# survives serialization/deserialization.
|
|
74
|
+
#
|
|
75
|
+
# @param job [ActiveJob::Base] The job being enqueued
|
|
76
|
+
# @return [void]
|
|
77
|
+
def propagate_correlation(job)
|
|
78
|
+
correlation_id = EzLogsAgent::Correlation.current
|
|
79
|
+
return unless correlation_id && !correlation_id.empty?
|
|
80
|
+
|
|
81
|
+
job.ezlogs_correlation_id = correlation_id
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
EzLogsAgent::Logger.error("[ActiveJobCapturer] Correlation propagation failed: #{e.class} - #{e.message}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Captures job execution as a background_job event.
|
|
87
|
+
#
|
|
88
|
+
# Runs at execution-time (when job executes).
|
|
89
|
+
# Skips capture if job uses Sidekiq adapter (prevents double events).
|
|
90
|
+
#
|
|
91
|
+
# IMPORTANT: When jobs run inline (Async adapter, perform_now, test adapter),
|
|
92
|
+
# they execute in the same thread as the caller (HTTP request or parent job).
|
|
93
|
+
# We must save and restore the previous correlation to avoid clearing the
|
|
94
|
+
# outer context's correlation. This applies to:
|
|
95
|
+
# - Development with Async adapter
|
|
96
|
+
# - Production with perform_now calls
|
|
97
|
+
# - Test environments
|
|
98
|
+
# - Any synchronous job execution
|
|
99
|
+
#
|
|
100
|
+
# @param job [ActiveJob::Base] The job being executed
|
|
101
|
+
# @param block [Proc] The job execution block
|
|
102
|
+
# @return [Object] The result of the job execution
|
|
103
|
+
def capture_execution(job, block)
|
|
104
|
+
return block.call unless EzLogsAgent.configuration.capture_jobs
|
|
105
|
+
|
|
106
|
+
if sidekiq_adapter?(job)
|
|
107
|
+
EzLogsAgent::Logger.debug("[ActiveJobCapturer] Skipping capture (Sidekiq adapter)")
|
|
108
|
+
return block.call
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if excluded_job_class?(job)
|
|
112
|
+
EzLogsAgent::Logger.debug("[ActiveJobCapturer] Skipping capture (excluded job class: #{job.class.name})")
|
|
113
|
+
return block.call
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Save the previous correlation (from HTTP middleware or parent job)
|
|
117
|
+
# so we can restore it after the job completes
|
|
118
|
+
previous_correlation = EzLogsAgent::Correlation.current
|
|
119
|
+
|
|
120
|
+
# Use job's propagated correlation, fall back to current context, or generate new
|
|
121
|
+
correlation_id = extract_correlation(job) || previous_correlation || EzLogsAgent::Correlation.generate
|
|
122
|
+
EzLogsAgent::Correlation.current = correlation_id
|
|
123
|
+
|
|
124
|
+
start_time = Time.now
|
|
125
|
+
result = block.call
|
|
126
|
+
duration_ms = ((Time.now - start_time) * 1000).to_i
|
|
127
|
+
|
|
128
|
+
capture_success(job, correlation_id, duration_ms, start_time)
|
|
129
|
+
result
|
|
130
|
+
rescue StandardError => error
|
|
131
|
+
capture_failure(job, correlation_id, error, start_time)
|
|
132
|
+
raise
|
|
133
|
+
ensure
|
|
134
|
+
# Restore previous correlation instead of unconditionally clearing.
|
|
135
|
+
# This is critical for inline jobs that run in the same thread as the caller.
|
|
136
|
+
if previous_correlation
|
|
137
|
+
EzLogsAgent::Correlation.current = previous_correlation
|
|
138
|
+
else
|
|
139
|
+
EzLogsAgent::Correlation.clear
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
# Installs serialization hooks on ActiveJob::Base to persist correlation_id
|
|
146
|
+
# across job serialization/deserialization.
|
|
147
|
+
#
|
|
148
|
+
# This is required because ActiveJob's metadata hash is NOT automatically
|
|
149
|
+
# serialized. We override `serialize` and `deserialize` to include our
|
|
150
|
+
# correlation_id in the job data.
|
|
151
|
+
#
|
|
152
|
+
# @return [void]
|
|
153
|
+
def install_serialization_hooks
|
|
154
|
+
return if @serialization_installed
|
|
155
|
+
|
|
156
|
+
ActiveJob::Base.class_eval do
|
|
157
|
+
attr_accessor :ezlogs_correlation_id
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
ActiveJob::Base.prepend(Module.new do
|
|
161
|
+
def serialize
|
|
162
|
+
super.merge("ezlogs_correlation_id" => @ezlogs_correlation_id)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def deserialize(job_data)
|
|
166
|
+
super
|
|
167
|
+
@ezlogs_correlation_id = job_data["ezlogs_correlation_id"]
|
|
168
|
+
end
|
|
169
|
+
end)
|
|
170
|
+
|
|
171
|
+
@serialization_installed = true
|
|
172
|
+
EzLogsAgent::Logger.debug("[ActiveJobCapturer] Serialization hooks installed")
|
|
173
|
+
rescue StandardError => e
|
|
174
|
+
EzLogsAgent::Logger.error("[ActiveJobCapturer] Failed to install serialization hooks: #{e.class} - #{e.message}")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Checks if job uses Sidekiq adapter.
|
|
178
|
+
#
|
|
179
|
+
# @param job [ActiveJob::Base] The job instance
|
|
180
|
+
# @return [Boolean] true if Sidekiq adapter, false otherwise
|
|
181
|
+
def sidekiq_adapter?(job)
|
|
182
|
+
return false unless defined?(ActiveJob::QueueAdapters::SidekiqAdapter)
|
|
183
|
+
|
|
184
|
+
job.class.queue_adapter.is_a?(ActiveJob::QueueAdapters::SidekiqAdapter)
|
|
185
|
+
rescue StandardError
|
|
186
|
+
false
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Checks if job class is in the excluded list.
|
|
190
|
+
#
|
|
191
|
+
# @param job [ActiveJob::Base] The job instance
|
|
192
|
+
# @return [Boolean] true if excluded, false otherwise
|
|
193
|
+
def excluded_job_class?(job)
|
|
194
|
+
job_class_name = job.class.name
|
|
195
|
+
EzLogsAgent.configuration.all_excluded_job_classes.include?(job_class_name)
|
|
196
|
+
rescue StandardError
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Extracts correlation_id from job.
|
|
201
|
+
#
|
|
202
|
+
# @param job [ActiveJob::Base] The job instance
|
|
203
|
+
# @return [String, nil] The correlation_id if present
|
|
204
|
+
def extract_correlation(job)
|
|
205
|
+
return nil unless job.respond_to?(:ezlogs_correlation_id)
|
|
206
|
+
|
|
207
|
+
correlation = job.ezlogs_correlation_id
|
|
208
|
+
correlation if correlation && !correlation.empty?
|
|
209
|
+
rescue StandardError
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Captures successful job execution.
|
|
214
|
+
#
|
|
215
|
+
# @param job [ActiveJob::Base] The job instance
|
|
216
|
+
# @param correlation_id [String] The correlation ID
|
|
217
|
+
# @param duration_ms [Integer] Job execution duration in milliseconds
|
|
218
|
+
# @param start_time [Time] Job start time
|
|
219
|
+
# @return [void]
|
|
220
|
+
def capture_success(job, correlation_id, duration_ms, start_time)
|
|
221
|
+
event = EzLogsAgent::EventBuilder.build(
|
|
222
|
+
source_type: :background_job,
|
|
223
|
+
source_data: extract_job_data(job),
|
|
224
|
+
outcome: :success,
|
|
225
|
+
correlation_id: correlation_id,
|
|
226
|
+
duration_ms: duration_ms,
|
|
227
|
+
timestamp: start_time
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
EzLogsAgent::Buffer.push(event)
|
|
231
|
+
rescue StandardError => e
|
|
232
|
+
EzLogsAgent::Logger.error("[ActiveJobCapturer] Failed to capture success event: #{e.class} - #{e.message}")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Captures failed job execution.
|
|
236
|
+
#
|
|
237
|
+
# @param job [ActiveJob::Base] The job instance
|
|
238
|
+
# @param correlation_id [String] The correlation ID
|
|
239
|
+
# @param error [StandardError] The error that caused the failure
|
|
240
|
+
# @param start_time [Time] Job start time
|
|
241
|
+
# @return [void]
|
|
242
|
+
def capture_failure(job, correlation_id, error, start_time)
|
|
243
|
+
event = EzLogsAgent::EventBuilder.build(
|
|
244
|
+
source_type: :background_job,
|
|
245
|
+
source_data: extract_job_data(job),
|
|
246
|
+
outcome: :failure,
|
|
247
|
+
correlation_id: correlation_id,
|
|
248
|
+
error_message: "#{error.class}: #{error.message}",
|
|
249
|
+
timestamp: start_time
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
EzLogsAgent::Buffer.push(event)
|
|
253
|
+
rescue StandardError => e
|
|
254
|
+
EzLogsAgent::Logger.error("[ActiveJobCapturer] Failed to capture failure event: #{e.class} - #{e.message}")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Extracts relevant job data for event source_data.
|
|
258
|
+
#
|
|
259
|
+
# @param job [ActiveJob::Base] The job instance
|
|
260
|
+
# @return [Hash] Job metadata for source_data
|
|
261
|
+
def extract_job_data(job)
|
|
262
|
+
{
|
|
263
|
+
job_class: job.class.name,
|
|
264
|
+
queue: job.queue_name
|
|
265
|
+
}.compact
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|