pigeon-rb 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/README.md +343 -0
- data/lib/pigeon/active_job_integration.rb +32 -0
- data/lib/pigeon/api.rb +200 -0
- data/lib/pigeon/configuration.rb +161 -0
- data/lib/pigeon/core.rb +104 -0
- data/lib/pigeon/encryption.rb +213 -0
- data/lib/pigeon/generators/hanami/migration_generator.rb +89 -0
- data/lib/pigeon/generators/rails/install_generator.rb +32 -0
- data/lib/pigeon/generators/rails/migration_generator.rb +20 -0
- data/lib/pigeon/generators/rails/templates/create_outbox_messages.rb.erb +34 -0
- data/lib/pigeon/generators/rails/templates/initializer.rb.erb +88 -0
- data/lib/pigeon/hanami_integration.rb +78 -0
- data/lib/pigeon/health_check/kafka.rb +37 -0
- data/lib/pigeon/health_check/processor.rb +70 -0
- data/lib/pigeon/health_check/queue.rb +69 -0
- data/lib/pigeon/health_check.rb +63 -0
- data/lib/pigeon/logging/structured_logger.rb +181 -0
- data/lib/pigeon/metrics/collector.rb +200 -0
- data/lib/pigeon/mock_producer.rb +18 -0
- data/lib/pigeon/models/adapters/active_record_adapter.rb +133 -0
- data/lib/pigeon/models/adapters/rom_adapter.rb +150 -0
- data/lib/pigeon/models/outbox_message.rb +182 -0
- data/lib/pigeon/monitoring.rb +113 -0
- data/lib/pigeon/outbox.rb +61 -0
- data/lib/pigeon/processor/background_processor.rb +109 -0
- data/lib/pigeon/processor.rb +798 -0
- data/lib/pigeon/publisher.rb +524 -0
- data/lib/pigeon/railtie.rb +29 -0
- data/lib/pigeon/schema.rb +35 -0
- data/lib/pigeon/security.rb +30 -0
- data/lib/pigeon/serializer.rb +77 -0
- data/lib/pigeon/tasks/pigeon.rake +64 -0
- data/lib/pigeon/trace_api.rb +37 -0
- data/lib/pigeon/tracing/core.rb +119 -0
- data/lib/pigeon/tracing/messaging.rb +144 -0
- data/lib/pigeon/tracing.rb +107 -0
- data/lib/pigeon/version.rb +5 -0
- data/lib/pigeon.rb +52 -0
- metadata +127 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :pigeon do # rubocop:disable Metrics/BlockLength
|
4
|
+
desc "Process pending outbox messages"
|
5
|
+
task process: :environment do
|
6
|
+
batch_size = ENV.fetch("BATCH_SIZE", 100).to_i
|
7
|
+
puts "Processing pending outbox messages (batch size: #{batch_size})..."
|
8
|
+
stats = Pigeon.processor.process_pending(batch_size: batch_size)
|
9
|
+
puts "Processed #{stats[:processed]} messages: #{stats[:succeeded]} succeeded, #{stats[:failed]} failed"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Start background processing of outbox messages"
|
13
|
+
task start: :environment do
|
14
|
+
batch_size = ENV.fetch("BATCH_SIZE", 100).to_i
|
15
|
+
interval = ENV.fetch("INTERVAL", 5).to_i
|
16
|
+
thread_count = ENV.fetch("THREAD_COUNT", 2).to_i
|
17
|
+
|
18
|
+
puts "Starting background processing of outbox messages..."
|
19
|
+
puts "Batch size: #{batch_size}, Interval: #{interval}s, Threads: #{thread_count}"
|
20
|
+
|
21
|
+
processor = Pigeon.processor
|
22
|
+
if processor.start_processing(batch_size: batch_size, interval: interval, thread_count: thread_count)
|
23
|
+
puts "Background processing started successfully"
|
24
|
+
|
25
|
+
# Keep the process running
|
26
|
+
trap("INT") do
|
27
|
+
processor.stop_processing
|
28
|
+
puts "\nStopped processing"
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
trap("TERM") do
|
32
|
+
processor.stop_processing
|
33
|
+
puts "\nStopped processing"
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sleep indefinitely
|
38
|
+
loop { sleep }
|
39
|
+
else
|
40
|
+
puts "Failed to start background processing"
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Clean up processed outbox messages"
|
46
|
+
task cleanup: :environment do
|
47
|
+
older_than = ENV.fetch("OLDER_THAN", 7).to_i
|
48
|
+
puts "Cleaning up processed outbox messages older than #{older_than} days..."
|
49
|
+
count = Pigeon.processor.cleanup_processed(older_than: older_than)
|
50
|
+
puts "Cleaned up #{count} messages"
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Generate outbox message migration for Rails"
|
54
|
+
task :install, [:framework] do |_t, args|
|
55
|
+
if args[:framework] == "rails"
|
56
|
+
system "rails generate pigeon:rails:install"
|
57
|
+
elsif args[:framework] == "hanami"
|
58
|
+
system "hanami generate pigeon:rails:install"
|
59
|
+
else
|
60
|
+
puts "Invalid framework: #{args[:framework]}. Valid frameworks are: rails, hanami"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pigeon
|
4
|
+
# Tracing API for Pigeon
|
5
|
+
module TraceAPI
|
6
|
+
# Initialize OpenTelemetry tracing
|
7
|
+
# @param service_name [String] Service name for traces
|
8
|
+
# @param exporter [OpenTelemetry::Exporter::OTLP::Exporter, nil] Optional custom exporter
|
9
|
+
# @return [Boolean] Whether initialization was successful
|
10
|
+
def self.init_tracing(service_name: "pigeon", exporter: nil)
|
11
|
+
Tracing.init(service_name: service_name, exporter: exporter)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check if OpenTelemetry tracing is available
|
15
|
+
# @return [Boolean] Whether OpenTelemetry is available
|
16
|
+
def self.tracing_available?
|
17
|
+
Tracing.available?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the OpenTelemetry tracer
|
21
|
+
# @param name [String] Tracer name
|
22
|
+
# @return [OpenTelemetry::Tracer, nil] Tracer or nil if OpenTelemetry is not available
|
23
|
+
def self.tracer(name = "pigeon")
|
24
|
+
Tracing.tracer(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a span for a block of code
|
28
|
+
# @param name [String] Span name
|
29
|
+
# @param attributes [Hash] Span attributes
|
30
|
+
# @param kind [Symbol] Span kind (:internal, :server, :client, :producer, :consumer)
|
31
|
+
# @yield Block to execute within the span
|
32
|
+
# @return [Object] Result of the block
|
33
|
+
def self.with_span(name, attributes: {}, kind: :internal, &)
|
34
|
+
Tracing.with_span(name, attributes: attributes, kind: kind, &)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pigeon
|
4
|
+
module Tracing
|
5
|
+
# Core tracing functionality
|
6
|
+
module Core
|
7
|
+
# Check if OpenTelemetry is available
|
8
|
+
# @return [Boolean] Whether OpenTelemetry is available
|
9
|
+
def self.available?
|
10
|
+
defined?(OpenTelemetry)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Initialize OpenTelemetry tracing
|
14
|
+
# @param service_name [String] Service name for traces
|
15
|
+
# @param exporter [OpenTelemetry::Exporter::OTLP::Exporter, nil] Optional custom exporter
|
16
|
+
# @return [Boolean] Whether initialization was successful
|
17
|
+
def self.init(service_name: "pigeon", exporter: nil)
|
18
|
+
return false unless available?
|
19
|
+
|
20
|
+
begin
|
21
|
+
# Configure the SDK with default exporters
|
22
|
+
OpenTelemetry::SDK.configure do |c|
|
23
|
+
c.service_name = service_name
|
24
|
+
c.use_all # Use all available instrumentation
|
25
|
+
c.add_span_processor(
|
26
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
27
|
+
exporter || OpenTelemetry::Exporter::OTLP::Exporter.new
|
28
|
+
)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
rescue StandardError => e
|
34
|
+
Pigeon.config.logger.error("Failed to initialize OpenTelemetry: #{e.message}")
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the OpenTelemetry tracer
|
40
|
+
# @param name [String] Tracer name
|
41
|
+
# @return [OpenTelemetry::Tracer, nil] Tracer or nil if OpenTelemetry is not available
|
42
|
+
def self.tracer(name = "pigeon")
|
43
|
+
return nil unless available?
|
44
|
+
|
45
|
+
OpenTelemetry.tracer_provider.tracer(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a span for a block of code
|
49
|
+
# @param name [String] Span name
|
50
|
+
# @param attributes [Hash] Span attributes
|
51
|
+
# @param kind [Symbol] Span kind (:internal, :server, :client, :producer, :consumer)
|
52
|
+
# @param parent_context [OpenTelemetry::Context, nil] Optional parent context
|
53
|
+
# @yield Block to execute within the span
|
54
|
+
# @return [Object] Result of the block
|
55
|
+
def self.with_span(name, attributes: {}, kind: :internal, parent_context: nil, &)
|
56
|
+
return yield unless available?
|
57
|
+
|
58
|
+
tracer = self.tracer
|
59
|
+
return yield unless tracer
|
60
|
+
|
61
|
+
parent_context ||= OpenTelemetry::Context.current
|
62
|
+
span_kind = span_kind_from_symbol(kind)
|
63
|
+
|
64
|
+
tracer.in_span(name, attributes: attributes, kind: span_kind, with_parent: parent_context, &)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extract trace context from headers
|
68
|
+
# @param headers [Hash] Headers containing trace context
|
69
|
+
# @return [OpenTelemetry::Context, nil] Extracted context or nil if not available
|
70
|
+
def self.extract_context(headers)
|
71
|
+
return nil unless available?
|
72
|
+
return nil unless headers
|
73
|
+
|
74
|
+
# Convert header keys to lowercase for case-insensitive matching
|
75
|
+
normalized_headers = {}
|
76
|
+
headers.each do |k, v|
|
77
|
+
normalized_headers[k.to_s.downcase] = v
|
78
|
+
end
|
79
|
+
|
80
|
+
carrier = OpenTelemetry::Context::Propagation::Rack::HeadersGetter.new(normalized_headers)
|
81
|
+
OpenTelemetry.propagation.extract(carrier)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Inject trace context into headers
|
85
|
+
# @param headers [Hash] Headers to inject trace context into
|
86
|
+
# @param context [OpenTelemetry::Context, nil] Context to inject (defaults to current)
|
87
|
+
# @return [Hash] Headers with injected trace context
|
88
|
+
def self.inject_context(headers = {}, context = nil)
|
89
|
+
return headers unless available?
|
90
|
+
|
91
|
+
headers ||= {}
|
92
|
+
context ||= OpenTelemetry::Context.current
|
93
|
+
carrier = OpenTelemetry::Context::Propagation::Rack::HeadersSetter.new(headers)
|
94
|
+
OpenTelemetry.propagation.inject(carrier, context: context)
|
95
|
+
headers
|
96
|
+
end
|
97
|
+
|
98
|
+
# Convert a symbol to an OpenTelemetry span kind
|
99
|
+
# @param kind [Symbol] Span kind symbol
|
100
|
+
# @return [Integer] OpenTelemetry span kind
|
101
|
+
def self.span_kind_from_symbol(kind)
|
102
|
+
return OpenTelemetry::Trace::SpanKind::INTERNAL unless available?
|
103
|
+
|
104
|
+
case kind
|
105
|
+
when :server
|
106
|
+
OpenTelemetry::Trace::SpanKind::SERVER
|
107
|
+
when :client
|
108
|
+
OpenTelemetry::Trace::SpanKind::CLIENT
|
109
|
+
when :producer
|
110
|
+
OpenTelemetry::Trace::SpanKind::PRODUCER
|
111
|
+
when :consumer
|
112
|
+
OpenTelemetry::Trace::SpanKind::CONSUMER
|
113
|
+
else
|
114
|
+
OpenTelemetry::Trace::SpanKind::INTERNAL
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pigeon
|
4
|
+
module Tracing
|
5
|
+
# Messaging-specific tracing functionality
|
6
|
+
module Messaging
|
7
|
+
# Create a span for publishing a message
|
8
|
+
# @param topic [String] Kafka topic
|
9
|
+
# @param payload [Hash, String] Message payload
|
10
|
+
# @param key [String, nil] Optional message key
|
11
|
+
# @param headers [Hash, nil] Optional message headers
|
12
|
+
# @param correlation_id [String, nil] Optional correlation ID
|
13
|
+
# @yield [span, context, headers] Block to execute within the span
|
14
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
15
|
+
# @yieldparam context [OpenTelemetry::Context] Span context
|
16
|
+
# @yieldparam headers [Hash] Headers with injected trace context
|
17
|
+
# @return [Object] Result of the block
|
18
|
+
def self.trace_publish(topic:, payload:, key: nil, headers: nil, correlation_id: nil)
|
19
|
+
return yield(nil, nil, headers || {}) unless Core.available?
|
20
|
+
|
21
|
+
# Create span attributes
|
22
|
+
attributes = publish_span_attributes(topic, payload, key, correlation_id)
|
23
|
+
|
24
|
+
# Create the span
|
25
|
+
Core.with_span("publish_message", attributes: attributes, kind: :producer) do |span|
|
26
|
+
# Get the current context with the span
|
27
|
+
context = OpenTelemetry::Context.current
|
28
|
+
|
29
|
+
# Inject trace context into headers
|
30
|
+
trace_headers = headers ? headers.dup : {}
|
31
|
+
Core.inject_context(trace_headers, context)
|
32
|
+
|
33
|
+
# Add correlation ID to span
|
34
|
+
span.add_attributes("messaging.correlation_id" => correlation_id) if correlation_id
|
35
|
+
|
36
|
+
# Yield to the block with the span, context, and headers
|
37
|
+
yield span, context, trace_headers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create span attributes for publishing
|
42
|
+
# @param topic [String] Kafka topic
|
43
|
+
# @param payload [Hash, String] Message payload
|
44
|
+
# @param key [String, nil] Optional message key
|
45
|
+
# @param correlation_id [String, nil] Optional correlation ID
|
46
|
+
# @return [Hash] Span attributes
|
47
|
+
def self.publish_span_attributes(topic, payload, key, correlation_id)
|
48
|
+
attributes = {
|
49
|
+
"messaging.system" => "kafka",
|
50
|
+
"messaging.destination" => topic,
|
51
|
+
"messaging.destination_kind" => "topic",
|
52
|
+
"messaging.operation" => "publish"
|
53
|
+
}
|
54
|
+
|
55
|
+
# Add optional attributes
|
56
|
+
attributes["messaging.message_id"] = correlation_id if correlation_id
|
57
|
+
attributes["messaging.kafka.key"] = key if key
|
58
|
+
attributes["messaging.message_payload_size_bytes"] = payload.bytesize if payload.respond_to?(:bytesize)
|
59
|
+
|
60
|
+
attributes
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create a span for processing a message
|
64
|
+
# @param message [Pigeon::Models::OutboxMessage] Message to process
|
65
|
+
# @yield [span] Block to execute within the span
|
66
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
67
|
+
# @return [Object] Result of the block
|
68
|
+
def self.trace_process(message)
|
69
|
+
return yield(nil) unless Core.available?
|
70
|
+
|
71
|
+
# Extract trace context from message headers if available
|
72
|
+
parent_context = message.headers ? Core.extract_context(message.headers) : nil
|
73
|
+
|
74
|
+
# Create span attributes
|
75
|
+
attributes = process_span_attributes(message)
|
76
|
+
|
77
|
+
# Create the span
|
78
|
+
Core.with_span(
|
79
|
+
"process_message",
|
80
|
+
attributes: attributes,
|
81
|
+
kind: :consumer,
|
82
|
+
parent_context: parent_context
|
83
|
+
) do |span|
|
84
|
+
# Add additional attributes
|
85
|
+
add_process_span_attributes(span, message)
|
86
|
+
|
87
|
+
# Yield to the block with the span
|
88
|
+
yield span
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create span attributes for processing
|
93
|
+
# @param message [Pigeon::Models::OutboxMessage] Message to process
|
94
|
+
# @return [Hash] Span attributes
|
95
|
+
def self.process_span_attributes(message)
|
96
|
+
attributes = {
|
97
|
+
"messaging.system" => "kafka",
|
98
|
+
"messaging.destination" => message.topic,
|
99
|
+
"messaging.destination_kind" => "topic",
|
100
|
+
"messaging.operation" => "process",
|
101
|
+
"messaging.kafka.key" => message.key,
|
102
|
+
"messaging.message_id" => message.id.to_s
|
103
|
+
}
|
104
|
+
|
105
|
+
# Add correlation ID if available
|
106
|
+
attributes["messaging.correlation_id"] = message.correlation_id if message.correlation_id
|
107
|
+
|
108
|
+
attributes
|
109
|
+
end
|
110
|
+
|
111
|
+
# Add additional attributes to a process span
|
112
|
+
# @param span [OpenTelemetry::Span] Span to add attributes to
|
113
|
+
# @param message [Pigeon::Models::OutboxMessage] Message being processed
|
114
|
+
# @return [void]
|
115
|
+
def self.add_process_span_attributes(span, message)
|
116
|
+
return unless span && message.partition
|
117
|
+
|
118
|
+
span.add_attributes({
|
119
|
+
"messaging.kafka.partition" => message.partition,
|
120
|
+
"messaging.kafka.message.retry_count" => message.retry_count
|
121
|
+
})
|
122
|
+
end
|
123
|
+
|
124
|
+
# Create a span for batch processing
|
125
|
+
# @param batch_size [Integer] Number of messages to process
|
126
|
+
# @yield [span] Block to execute within the span
|
127
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
128
|
+
# @return [Object] Result of the block
|
129
|
+
def self.trace_batch_process(batch_size, &)
|
130
|
+
return yield(nil) unless Core.available?
|
131
|
+
|
132
|
+
# Create span attributes
|
133
|
+
attributes = {
|
134
|
+
"messaging.system" => "kafka",
|
135
|
+
"messaging.operation" => "batch_process",
|
136
|
+
"messaging.batch.size" => batch_size
|
137
|
+
}
|
138
|
+
|
139
|
+
# Create the span
|
140
|
+
Core.with_span("process_batch", attributes: attributes, &)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "opentelemetry"
|
5
|
+
require "opentelemetry/sdk"
|
6
|
+
require "opentelemetry/exporter/otlp"
|
7
|
+
require "opentelemetry/instrumentation/all"
|
8
|
+
rescue LoadError
|
9
|
+
# OpenTelemetry is optional, so we can continue without it
|
10
|
+
end
|
11
|
+
|
12
|
+
require_relative "tracing/core"
|
13
|
+
require_relative "tracing/messaging"
|
14
|
+
|
15
|
+
module Pigeon
|
16
|
+
# Tracing module for Pigeon using OpenTelemetry
|
17
|
+
module Tracing
|
18
|
+
class << self
|
19
|
+
# Check if OpenTelemetry is available
|
20
|
+
# @return [Boolean] Whether OpenTelemetry is available
|
21
|
+
def available?
|
22
|
+
Core.available?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initialize OpenTelemetry tracing
|
26
|
+
# @param service_name [String] Service name for traces
|
27
|
+
# @param exporter [OpenTelemetry::Exporter::OTLP::Exporter, nil] Optional custom exporter
|
28
|
+
# @return [Boolean] Whether initialization was successful
|
29
|
+
def init(service_name: "pigeon", exporter: nil)
|
30
|
+
Core.init(service_name: service_name, exporter: exporter)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the OpenTelemetry tracer
|
34
|
+
# @param name [String] Tracer name
|
35
|
+
# @return [OpenTelemetry::Tracer, nil] Tracer or nil if OpenTelemetry is not available
|
36
|
+
def tracer(name = "pigeon")
|
37
|
+
Core.tracer(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a span for a block of code
|
41
|
+
# @param name [String] Span name
|
42
|
+
# @param attributes [Hash] Span attributes
|
43
|
+
# @param kind [Symbol] Span kind (:internal, :server, :client, :producer, :consumer)
|
44
|
+
# @param parent_context [OpenTelemetry::Context, nil] Optional parent context
|
45
|
+
# @yield Block to execute within the span
|
46
|
+
# @return [Object] Result of the block
|
47
|
+
def with_span(name, attributes: {}, kind: :internal, parent_context: nil, &)
|
48
|
+
Core.with_span(name, attributes: attributes, kind: kind, parent_context: parent_context, &)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Extract trace context from headers
|
52
|
+
# @param headers [Hash] Headers containing trace context
|
53
|
+
# @return [OpenTelemetry::Context, nil] Extracted context or nil if not available
|
54
|
+
def extract_context(headers)
|
55
|
+
Core.extract_context(headers)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Inject trace context into headers
|
59
|
+
# @param headers [Hash] Headers to inject trace context into
|
60
|
+
# @param context [OpenTelemetry::Context, nil] Context to inject (defaults to current)
|
61
|
+
# @return [Hash] Headers with injected trace context
|
62
|
+
def inject_context(headers = {}, context = nil)
|
63
|
+
Core.inject_context(headers, context)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create a span for publishing a message
|
67
|
+
# @param topic [String] Kafka topic
|
68
|
+
# @param payload [Hash, String] Message payload
|
69
|
+
# @param key [String, nil] Optional message key
|
70
|
+
# @param headers [Hash, nil] Optional message headers
|
71
|
+
# @param correlation_id [String, nil] Optional correlation ID
|
72
|
+
# @yield [span, context, headers] Block to execute within the span
|
73
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
74
|
+
# @yieldparam context [OpenTelemetry::Context] Span context
|
75
|
+
# @yieldparam headers [Hash] Headers with injected trace context
|
76
|
+
# @return [Object] Result of the block
|
77
|
+
def trace_publish(topic:, payload:, key: nil, headers: nil, correlation_id: nil, &)
|
78
|
+
Messaging.trace_publish(
|
79
|
+
topic: topic,
|
80
|
+
payload: payload,
|
81
|
+
key: key,
|
82
|
+
headers: headers,
|
83
|
+
correlation_id: correlation_id,
|
84
|
+
&
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create a span for processing a message
|
89
|
+
# @param message [Pigeon::Models::OutboxMessage] Message to process
|
90
|
+
# @yield [span] Block to execute within the span
|
91
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
92
|
+
# @return [Object] Result of the block
|
93
|
+
def trace_process(message, &)
|
94
|
+
Messaging.trace_process(message, &)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create a span for batch processing
|
98
|
+
# @param batch_size [Integer] Number of messages to process
|
99
|
+
# @yield [span] Block to execute within the span
|
100
|
+
# @yieldparam span [OpenTelemetry::Span] Active span
|
101
|
+
# @return [Object] Result of the block
|
102
|
+
def trace_batch_process(batch_size, &)
|
103
|
+
Messaging.trace_batch_process(batch_size, &)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/pigeon.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pigeon/version"
|
4
|
+
require "pigeon/logging/structured_logger"
|
5
|
+
require "pigeon/configuration"
|
6
|
+
require "pigeon/models/outbox_message"
|
7
|
+
require "pigeon/models/adapters/active_record_adapter"
|
8
|
+
require "pigeon/models/adapters/rom_adapter"
|
9
|
+
require "pigeon/serializer"
|
10
|
+
require "pigeon/encryption"
|
11
|
+
require "pigeon/tracing"
|
12
|
+
require "pigeon/publisher"
|
13
|
+
require "pigeon/processor"
|
14
|
+
require "pigeon/health_check"
|
15
|
+
require "karafka"
|
16
|
+
|
17
|
+
# Load Rails integration if Rails is defined
|
18
|
+
require "pigeon/railtie" if defined?(Rails)
|
19
|
+
|
20
|
+
# Load Hanami integration if Hanami is defined
|
21
|
+
require "pigeon/hanami_integration" if defined?(Hanami)
|
22
|
+
|
23
|
+
# Load generators if Rails is defined
|
24
|
+
if defined?(Rails)
|
25
|
+
require "pigeon/generators/rails/migration_generator"
|
26
|
+
require "pigeon/generators/rails/install_generator"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Load internal modules
|
30
|
+
require_relative "pigeon/mock_producer"
|
31
|
+
require_relative "pigeon/core"
|
32
|
+
require_relative "pigeon/schema"
|
33
|
+
require_relative "pigeon/security"
|
34
|
+
require_relative "pigeon/outbox"
|
35
|
+
require_relative "pigeon/monitoring"
|
36
|
+
require_relative "pigeon/trace_api"
|
37
|
+
require_relative "pigeon/api"
|
38
|
+
|
39
|
+
# Main module for the Pigeon gem
|
40
|
+
module Pigeon
|
41
|
+
class Error < StandardError; end
|
42
|
+
|
43
|
+
# Include API modules
|
44
|
+
class << self
|
45
|
+
include API::CoreAPI
|
46
|
+
include API::SchemaAPI
|
47
|
+
include API::SecurityAPI
|
48
|
+
include API::OutboxAPI
|
49
|
+
include API::MonitoringAPI
|
50
|
+
include API::TracingAPI
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pigeon-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Khai Le
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-07-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-configurable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: karafka
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.5'
|
55
|
+
description: A Ruby gem that implements the outbox pattern for Kafka message publishing
|
56
|
+
to ensure message durability and delivery reliability
|
57
|
+
email:
|
58
|
+
- khaile.to@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- lib/pigeon.rb
|
65
|
+
- lib/pigeon/active_job_integration.rb
|
66
|
+
- lib/pigeon/api.rb
|
67
|
+
- lib/pigeon/configuration.rb
|
68
|
+
- lib/pigeon/core.rb
|
69
|
+
- lib/pigeon/encryption.rb
|
70
|
+
- lib/pigeon/generators/hanami/migration_generator.rb
|
71
|
+
- lib/pigeon/generators/rails/install_generator.rb
|
72
|
+
- lib/pigeon/generators/rails/migration_generator.rb
|
73
|
+
- lib/pigeon/generators/rails/templates/create_outbox_messages.rb.erb
|
74
|
+
- lib/pigeon/generators/rails/templates/initializer.rb.erb
|
75
|
+
- lib/pigeon/hanami_integration.rb
|
76
|
+
- lib/pigeon/health_check.rb
|
77
|
+
- lib/pigeon/health_check/kafka.rb
|
78
|
+
- lib/pigeon/health_check/processor.rb
|
79
|
+
- lib/pigeon/health_check/queue.rb
|
80
|
+
- lib/pigeon/logging/structured_logger.rb
|
81
|
+
- lib/pigeon/metrics/collector.rb
|
82
|
+
- lib/pigeon/mock_producer.rb
|
83
|
+
- lib/pigeon/models/adapters/active_record_adapter.rb
|
84
|
+
- lib/pigeon/models/adapters/rom_adapter.rb
|
85
|
+
- lib/pigeon/models/outbox_message.rb
|
86
|
+
- lib/pigeon/monitoring.rb
|
87
|
+
- lib/pigeon/outbox.rb
|
88
|
+
- lib/pigeon/processor.rb
|
89
|
+
- lib/pigeon/processor/background_processor.rb
|
90
|
+
- lib/pigeon/publisher.rb
|
91
|
+
- lib/pigeon/railtie.rb
|
92
|
+
- lib/pigeon/schema.rb
|
93
|
+
- lib/pigeon/security.rb
|
94
|
+
- lib/pigeon/serializer.rb
|
95
|
+
- lib/pigeon/tasks/pigeon.rake
|
96
|
+
- lib/pigeon/trace_api.rb
|
97
|
+
- lib/pigeon/tracing.rb
|
98
|
+
- lib/pigeon/tracing/core.rb
|
99
|
+
- lib/pigeon/tracing/messaging.rb
|
100
|
+
- lib/pigeon/version.rb
|
101
|
+
homepage: https://github.com/khaile/pigeon
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata:
|
105
|
+
homepage_uri: https://github.com/khaile/pigeon
|
106
|
+
changelog_uri: https://github.com/khaile/pigeon/blob/main/CHANGELOG.md
|
107
|
+
rubygems_mfa_required: 'false'
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 3.3.8
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubygems_version: 3.5.22
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Kafka outbox pattern implementation for Ruby applications
|
127
|
+
test_files: []
|