eventhub-processor2 1.24.1 → 1.26.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.
@@ -0,0 +1,7 @@
1
+ <svg width="200" height="200" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
2
+ <g>
3
+ <circle cx="150" cy="95" r="62" fill="#ffffff" />
4
+ <circle cx="215" cy="210" r="48" fill="#ffffff" />
5
+ <circle cx="95" cy="210" r="36" fill="#ffffff" />
6
+ </g>
7
+ </svg>
data/lib/eventhub/base.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "uuidtools"
1
+ require "securerandom"
2
2
  require "json"
3
3
  require "base64"
4
4
  require "optparse"
@@ -11,6 +11,8 @@ require "celluloid"
11
11
  require_relative "version"
12
12
  require_relative "constant"
13
13
  require_relative "base_exception"
14
+ require_relative "correlation_id"
15
+ require_relative "execution_id"
14
16
  require_relative "logger"
15
17
  require_relative "helper"
16
18
  require_relative "sleeper"
@@ -24,6 +26,7 @@ require_relative "actor_watchdog"
24
26
  require_relative "actor_publisher"
25
27
  require_relative "actor_listener_amqp"
26
28
  require_relative "actor_listener_http"
29
+ require_relative "docs_renderer"
27
30
  require_relative "processor2"
28
31
 
29
32
  Celluloid.logger = nil
@@ -137,6 +137,16 @@ module EventHub
137
137
  tls_ca_certificates: [],
138
138
  verify_peer: false,
139
139
  show_bunny_logs: false,
140
+ http: {
141
+ bind_address: "localhost",
142
+ port: 8080,
143
+ base_path: "/svc/#{@name}",
144
+ docs: {
145
+ readme_path: nil,
146
+ changelog_path: nil
147
+ }
148
+ },
149
+ # deprecated: use http instead (kept for backward compatibility)
140
150
  heartbeat: {
141
151
  bind_address: "localhost",
142
152
  port: 8080,
@@ -0,0 +1,35 @@
1
+ # EventHub module
2
+ module EventHub
3
+ # Manages correlation_id for distributed tracing
4
+ # Storage mechanism can be swapped if needed (e.g., Thread.current -> Fiber storage)
5
+ module CorrelationId
6
+ class << self
7
+ def current
8
+ Thread.current[:eventhub_correlation_id]
9
+ end
10
+
11
+ def current=(value)
12
+ Thread.current[:eventhub_correlation_id] = value
13
+ end
14
+
15
+ def clear
16
+ Thread.current[:eventhub_correlation_id] = nil
17
+ end
18
+
19
+ # Execute block with correlation_id set, ensures cleanup
20
+ def with(correlation_id)
21
+ if correlation_id.nil? || correlation_id.to_s.empty?
22
+ yield
23
+ else
24
+ old_value = current
25
+ begin
26
+ self.current = correlation_id
27
+ yield
28
+ ensure
29
+ self.current = old_value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,184 @@
1
+ require "erb"
2
+ require "kramdown"
3
+
4
+ module EventHub
5
+ class DocsRenderer
6
+ ASSETS_PATH = File.expand_path("assets", __dir__)
7
+ TEMPLATES_PATH = File.expand_path("templates", __dir__)
8
+
9
+ DEFAULT_README_LOCATIONS = ["README.md", "doc/README.md"].freeze
10
+ DEFAULT_CHANGELOG_LOCATIONS = ["CHANGELOG.md", "doc/CHANGELOG.md"].freeze
11
+ DEFAULT_COMPANY_NAME = "Novartis"
12
+
13
+ DEFAULT_HTTP_RESOURCES = [:heartbeat, :version, :docs, :changelog, :configuration].freeze
14
+
15
+ def initialize(processor:, base_path:)
16
+ @processor = processor
17
+ @base_path = base_path
18
+ end
19
+
20
+ def render_readme
21
+ content = readme_html
22
+ render_layout(title: "README", content: content, content_class: "")
23
+ end
24
+
25
+ def render_changelog
26
+ content = changelog_html
27
+ render_layout(title: "CHANGELOG", content: content, content_class: "changelog")
28
+ end
29
+
30
+ def render_config
31
+ content = config_html
32
+ render_layout(title: "Configuration", content: content, content_class: "config")
33
+ end
34
+
35
+ def asset(name)
36
+ path = File.join(ASSETS_PATH, name)
37
+ return nil unless File.exist?(path)
38
+ File.read(path)
39
+ end
40
+
41
+ private
42
+
43
+ def readme_html
44
+ return @processor.readme_as_html if @processor.class.method_defined?(:readme_as_html)
45
+
46
+ markdown = load_markdown(:readme)
47
+ markdown_to_html(markdown)
48
+ end
49
+
50
+ def changelog_html
51
+ return @processor.changelog_as_html if @processor.class.method_defined?(:changelog_as_html)
52
+
53
+ markdown = load_markdown(:changelog)
54
+ markdown_to_html(markdown)
55
+ end
56
+
57
+ def load_markdown(type)
58
+ config_path = case type
59
+ when :readme
60
+ EventHub::Configuration.server.dig(:http, :docs, :readme_path)
61
+ when :changelog
62
+ EventHub::Configuration.server.dig(:http, :docs, :changelog_path)
63
+ end
64
+
65
+ if config_path && File.exist?(config_path)
66
+ return File.read(config_path)
67
+ end
68
+
69
+ locations = case type
70
+ when :readme
71
+ DEFAULT_README_LOCATIONS
72
+ when :changelog
73
+ DEFAULT_CHANGELOG_LOCATIONS
74
+ end
75
+
76
+ locations.each do |location|
77
+ path = File.join(Dir.pwd, location)
78
+ return File.read(path) if File.exist?(path)
79
+ end
80
+
81
+ "No #{(type == :readme) ? "README" : "CHANGELOG"} available."
82
+ end
83
+
84
+ def config_html
85
+ return @processor.configuration_as_html if @processor&.class&.method_defined?(:configuration_as_html)
86
+
87
+ config = EventHub::Configuration.config_data
88
+ return "<p>No configuration available.</p>" if config.nil? || config.empty?
89
+
90
+ intro = "<h1>Configuration</h1>" \
91
+ "<p>Active configuration for the <strong>#{ERB::Util.html_escape(EventHub::Configuration.environment)}</strong> environment. " \
92
+ "Sensitive values such as passwords, tokens, and keys are automatically redacted.</p>"
93
+
94
+ intro + config_to_html_table(config)
95
+ end
96
+
97
+ def config_to_html_table(hash, depth = 0)
98
+ rows = hash.map do |key, value|
99
+ if value.is_a?(Hash)
100
+ "<tr class=\"is-section\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(key)}</strong></td></tr>\n" \
101
+ "#{config_to_html_table(value, depth + 1)}"
102
+ elsif value.is_a?(Array)
103
+ format_array_rows(key, value, depth)
104
+ else
105
+ display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(value.to_s)
106
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}</td><td>#{display_value}</td></tr>"
107
+ end
108
+ end.join("\n")
109
+
110
+ if depth == 0
111
+ "<table class=\"table is-bordered is-striped is-fullwidth config-table\">\n<thead><tr><th>Key</th><th>Value</th></tr></thead>\n<tbody>\n#{rows}\n</tbody>\n</table>"
112
+ else
113
+ rows
114
+ end
115
+ end
116
+
117
+ def format_array_rows(key, array, depth)
118
+ if array.any? { |item| item.is_a?(Hash) }
119
+ array.each_with_index.map do |item, index|
120
+ if item.is_a?(Hash)
121
+ "<tr class=\"is-section\"><td colspan=\"2\"><strong>#{ERB::Util.html_escape(key)}[#{index}]</strong></td></tr>\n" \
122
+ "#{config_to_html_table(item, depth + 1)}"
123
+ else
124
+ display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(item.to_s)
125
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}[#{index}]</td><td>#{display_value}</td></tr>"
126
+ end
127
+ end.join("\n")
128
+ else
129
+ display_value = sensitive_key?(key) ? "<span class=\"redacted\">[REDACTED]</span>" : ERB::Util.html_escape(array.join(", "))
130
+ "<tr><td class=\"config-key\">#{ERB::Util.html_escape(key)}</td><td>#{display_value}</td></tr>"
131
+ end
132
+ end
133
+
134
+ DEFAULT_SENSITIVE_KEYS = %w[password secret token api_key credential].freeze
135
+
136
+ def sensitive_key?(key)
137
+ keys = if @processor&.class&.method_defined?(:sensitive_keys)
138
+ @processor.sensitive_keys
139
+ else
140
+ DEFAULT_SENSITIVE_KEYS
141
+ end
142
+ keys.any? { |pattern| key.to_s.downcase == pattern.downcase }
143
+ end
144
+
145
+ def markdown_to_html(markdown)
146
+ Kramdown::Document.new(markdown).to_html
147
+ end
148
+
149
+ def render_layout(title:, content:, content_class: "")
150
+ template_path = File.join(TEMPLATES_PATH, "layout.erb")
151
+ template = File.read(template_path)
152
+
153
+ processor_name = EventHub::Configuration.name
154
+ version = processor_version
155
+ environment = EventHub::Configuration.environment
156
+ company_name = processor_company_name
157
+ base_path = @base_path
158
+ year = Time.now.year
159
+ http_resources = processor_http_resources
160
+ bulma_css = asset("bulma.min.css")
161
+ app_css = asset("app.css")
162
+
163
+ ERB.new(template).result(binding)
164
+ end
165
+
166
+ def processor_version
167
+ return "?.?.?" unless @processor
168
+ return "?.?.?" unless @processor.class.method_defined?(:version)
169
+ @processor.version
170
+ end
171
+
172
+ def processor_company_name
173
+ return DEFAULT_COMPANY_NAME unless @processor
174
+ return DEFAULT_COMPANY_NAME unless @processor.class.method_defined?(:company_name)
175
+ @processor.company_name
176
+ end
177
+
178
+ def processor_http_resources
179
+ return DEFAULT_HTTP_RESOURCES unless @processor
180
+ return DEFAULT_HTTP_RESOURCES unless @processor.class.method_defined?(:http_resources)
181
+ @processor.http_resources
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,20 @@
1
+ # EventHub module
2
+ module EventHub
3
+ # ExecutionId module for storing the current message's execution_id
4
+ # in thread-local storage for distributed tracing.
5
+ module ExecutionId
6
+ class << self
7
+ def current
8
+ Thread.current[:eventhub_execution_id]
9
+ end
10
+
11
+ def current=(value)
12
+ Thread.current[:eventhub_execution_id] = value
13
+ end
14
+
15
+ def clear
16
+ Thread.current[:eventhub_execution_id] = nil
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,21 +1,54 @@
1
1
  # EventHub module
2
2
  module EventHub
3
+ # Logger proxy that automatically includes correlation_id in structured log output
4
+ class LoggerProxy
5
+ def initialize(logger)
6
+ @logger = logger
7
+ end
8
+
9
+ %i[debug info warn error fatal unknown].each do |level|
10
+ define_method(level) do |message = nil, &block|
11
+ message = block.call if block
12
+ correlation_id = CorrelationId.current
13
+ execution_id = ExecutionId.current
14
+ if correlation_id || execution_id
15
+ log_hash = {message: message}
16
+ log_hash[:correlation_id] = correlation_id if correlation_id
17
+ log_hash[:execution_id] = execution_id if execution_id
18
+ @logger.send(level, log_hash)
19
+ else
20
+ @logger.send(level, message)
21
+ end
22
+ end
23
+ end
24
+
25
+ def method_missing(method, *args, &block)
26
+ @logger.send(method, *args, &block)
27
+ end
28
+
29
+ def respond_to_missing?(method, include_private = false)
30
+ @logger.respond_to?(method, include_private)
31
+ end
32
+ end
33
+
3
34
  def self.logger
4
35
  unless defined?(@logger)
5
- @logger = ::EventHub::Components::MultiLogger.new
36
+ base_logger = ::EventHub::Components::MultiLogger.new
6
37
 
7
38
  if Configuration.console_log_only
8
- @logger.add_device(
39
+ base_logger.add_device(
9
40
  EventHub::Components::Logger.logstash_cloud(Configuration.name,
10
41
  Configuration.environment)
11
42
  )
12
43
  else
13
- @logger.add_device(Logger.new($stdout))
14
- @logger.add_device(
44
+ base_logger.add_device(Logger.new($stdout))
45
+ base_logger.add_device(
15
46
  EventHub::Components::Logger.logstash(Configuration.name,
16
47
  Configuration.environment)
17
48
  )
18
49
  end
50
+
51
+ @logger = LoggerProxy.new(base_logger)
19
52
  end
20
53
  @logger
21
54
  end
@@ -62,7 +62,7 @@ module EventHub
62
62
  @raw = raw
63
63
 
64
64
  # set message defaults, that we have required headers
65
- @header.set("message_id", UUIDTools::UUID.timestamp_create.to_s, false)
65
+ @header.set("message_id", SecureRandom.uuid, false)
66
66
  @header.set("version", VERSION, false)
67
67
  @header.set("created_at", now_stamp, false)
68
68
 
@@ -71,8 +71,7 @@ module EventHub
71
71
  @header.set("origin.site_id", "undefined", false)
72
72
 
73
73
  @header.set("process.name", "undefined", false)
74
- @header.set("process.execution_id",
75
- UUIDTools::UUID.timestamp_create.to_s, false)
74
+ @header.set("process.execution_id", CorrelationId.current || SecureRandom.uuid, false)
76
75
  @header.set("process.step_position", 0, false)
77
76
 
78
77
  @header.set("status.retried_count", 0, false)
@@ -137,7 +136,7 @@ module EventHub
137
136
  copied_header = Marshal.load(Marshal.dump(header))
138
137
  copied_body = Marshal.load(Marshal.dump(body))
139
138
 
140
- copied_header.set("message_id", UUIDTools::UUID.timestamp_create.to_s)
139
+ copied_header.set("message_id", SecureRandom.uuid)
141
140
  copied_header.set("created_at", now_stamp)
142
141
  copied_header.set("status.code", status_code)
143
142
 
@@ -32,6 +32,7 @@ module EventHub
32
32
 
33
33
  def start
34
34
  EventHub.logger.info("#{Configuration.name} (#{version}): has been started")
35
+ log_general_settings
35
36
 
36
37
  before_start
37
38
  main_event_loop
@@ -76,6 +77,15 @@ module EventHub
76
77
 
77
78
  private
78
79
 
80
+ def log_general_settings
81
+ console = Configuration.console_log_only ? "yes" : "no"
82
+ settings = [
83
+ Configuration.environment,
84
+ "console=#{console}"
85
+ ].join(", ")
86
+ EventHub.logger.info("Settings [#{settings}]")
87
+ end
88
+
79
89
  def setup_signal_handler
80
90
  # have a re-entrant signal handler by just using a simple array
81
91
  # https://www.sitepoint.com/the-self-pipe-trick-explained/
@@ -88,7 +98,7 @@ module EventHub
88
98
  @config = Celluloid::Supervision::Configuration.define([
89
99
  {type: ActorHeartbeat, as: :actor_heartbeat, args: [self]},
90
100
  {type: ActorListenerAmqp, as: :actor_listener_amqp, args: [self]},
91
- {type: ActorListenerHttp, as: :actor_listener_http, args: []}
101
+ {type: ActorListenerHttp, as: :actor_listener_http, args: [{processor: self}]}
92
102
  ])
93
103
 
94
104
  sleeper = @sleeper
@@ -0,0 +1,63 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= title %> - <%= processor_name %></title>
7
+ <style>
8
+ <%= bulma_css %>
9
+ <%= app_css %>
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
14
+ <div class="container">
15
+ <div class="navbar-brand">
16
+ <a class="navbar-item" href="<%= base_path %>/docs">
17
+ <img src="<%= base_path %>/assets/logo.svg" alt="Logo" class="logo">
18
+ <span class="processor-name"><%= processor_name %></span>
19
+ </a>
20
+ </div>
21
+ <div class="navbar-menu">
22
+ <div class="navbar-end">
23
+ <% if http_resources.include?(:configuration) %>
24
+ <a class="navbar-item" href="<%= base_path %>/docs/configuration">
25
+ Configuration
26
+ </a>
27
+ <% end %>
28
+ <% if http_resources.include?(:changelog) %>
29
+ <a class="navbar-item" href="<%= base_path %>/docs/changelog">
30
+ Changelog
31
+ </a>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </nav>
37
+
38
+ <section class="section">
39
+ <div class="container">
40
+ <div class="content <%= content_class %>">
41
+ <%= content %>
42
+ </div>
43
+ </div>
44
+ </section>
45
+
46
+ <footer class="footer">
47
+ <div class="container">
48
+ <div class="level">
49
+ <div class="level-left">
50
+ <div class="level-item">
51
+ <span><strong><%= processor_name %></strong> <%= version %><br><%= environment %></span>
52
+ </div>
53
+ </div>
54
+ <div class="level-right">
55
+ <div class="level-item">
56
+ <span><%= company_name %> &copy; <%= year %></span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </footer>
62
+ </body>
63
+ </html>
@@ -1,3 +1,3 @@
1
1
  module EventHub
2
- VERSION = "1.24.1".freeze
2
+ VERSION = "1.26.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eventhub-processor2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.24.1
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steiner, Thomas
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0.4'
68
- - !ruby/object:Gem::Dependency
69
- name: uuidtools
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '3.0'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '3.0'
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: base64
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -108,19 +94,19 @@ dependencies:
108
94
  - !ruby/object:Gem::Version
109
95
  version: '1.6'
110
96
  - !ruby/object:Gem::Dependency
111
- name: bundler
97
+ name: kramdown
112
98
  requirement: !ruby/object:Gem::Requirement
113
99
  requirements:
114
100
  - - "~>"
115
101
  - !ruby/object:Gem::Version
116
- version: '2.3'
117
- type: :development
102
+ version: '2.4'
103
+ type: :runtime
118
104
  prerelease: false
119
105
  version_requirements: !ruby/object:Gem::Requirement
120
106
  requirements:
121
107
  - - "~>"
122
108
  - !ruby/object:Gem::Version
123
- version: '2.3'
109
+ version: '2.4'
124
110
  - !ruby/object:Gem::Dependency
125
111
  name: rake
126
112
  requirement: !ruby/object:Gem::Requirement
@@ -185,8 +171,8 @@ extensions: []
185
171
  extra_rdoc_files: []
186
172
  files:
187
173
  - ".github/dependabot.yml"
188
- - ".github/workflows/cd.yml"
189
- - ".github/workflows/ci.yml"
174
+ - ".github/workflows/01_test.yml"
175
+ - ".github/workflows/02_test_build_and_release.yml"
190
176
  - ".gitignore"
191
177
  - ".rspec"
192
178
  - ".tool-versions"
@@ -204,6 +190,7 @@ files:
204
190
  - docker/rabbitmq.config
205
191
  - docker/reset
206
192
  - eventhub-processor2.gemspec
193
+ - example/CHANGELOG.md
207
194
  - example/README.md
208
195
  - example/config/receiver.json
209
196
  - example/config/router.json
@@ -217,11 +204,17 @@ files:
217
204
  - lib/eventhub/actor_listener_http.rb
218
205
  - lib/eventhub/actor_publisher.rb
219
206
  - lib/eventhub/actor_watchdog.rb
207
+ - lib/eventhub/assets/app.css
208
+ - lib/eventhub/assets/bulma.min.css
209
+ - lib/eventhub/assets/logo.svg
220
210
  - lib/eventhub/base.rb
221
211
  - lib/eventhub/base_exception.rb
222
212
  - lib/eventhub/configuration.rb
223
213
  - lib/eventhub/constant.rb
224
214
  - lib/eventhub/consumer.rb
215
+ - lib/eventhub/correlation_id.rb
216
+ - lib/eventhub/docs_renderer.rb
217
+ - lib/eventhub/execution_id.rb
225
218
  - lib/eventhub/hash_extensions.rb
226
219
  - lib/eventhub/helper.rb
227
220
  - lib/eventhub/logger.rb
@@ -229,6 +222,7 @@ files:
229
222
  - lib/eventhub/processor2.rb
230
223
  - lib/eventhub/sleeper.rb
231
224
  - lib/eventhub/statistics.rb
225
+ - lib/eventhub/templates/layout.erb
232
226
  - lib/eventhub/version.rb
233
227
  homepage: https://github.com/thomis/eventhub-processor2
234
228
  licenses:
@@ -248,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
242
  - !ruby/object:Gem::Version
249
243
  version: '0'
250
244
  requirements: []
251
- rubygems_version: 3.6.9
245
+ rubygems_version: 4.0.6
252
246
  specification_version: 4
253
247
  summary: Next generation gem to build ruby based eventhub processor
254
248
  test_files: []
@@ -1,31 +0,0 @@
1
- name: cd
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- jobs:
9
-
10
- build:
11
- runs-on: ubuntu-latest
12
- permissions:
13
- id-token: write # Required for OIDC
14
- contents: write # Required for pushing tags
15
-
16
- steps:
17
- - name: Checkout current code
18
- uses: actions/checkout@v5
19
-
20
- - name: Set up Ruby
21
- uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: '3.4'
24
- bundler-cache: true
25
- cache-version: 1
26
-
27
- - name: Build gem
28
- run: gem build *.gemspec
29
-
30
- - name: Push to Rubygems
31
- uses: rubygems/release-gem@v1