eventhub-processor2 1.25.0 → 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
@@ -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
@@ -71,7 +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", SecureRandom.uuid, false)
74
+ @header.set("process.execution_id", CorrelationId.current || SecureRandom.uuid, false)
75
75
  @header.set("process.step_position", 0, false)
76
76
 
77
77
  @header.set("status.retried_count", 0, false)
@@ -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.25.0".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.25.0
4
+ version: 1.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steiner, Thomas
@@ -93,6 +93,20 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '1.6'
96
+ - !ruby/object:Gem::Dependency
97
+ name: kramdown
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.4'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.4'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: rake
98
112
  requirement: !ruby/object:Gem::Requirement
@@ -157,8 +171,8 @@ extensions: []
157
171
  extra_rdoc_files: []
158
172
  files:
159
173
  - ".github/dependabot.yml"
160
- - ".github/workflows/cd.yml"
161
- - ".github/workflows/ci.yml"
174
+ - ".github/workflows/01_test.yml"
175
+ - ".github/workflows/02_test_build_and_release.yml"
162
176
  - ".gitignore"
163
177
  - ".rspec"
164
178
  - ".tool-versions"
@@ -176,6 +190,7 @@ files:
176
190
  - docker/rabbitmq.config
177
191
  - docker/reset
178
192
  - eventhub-processor2.gemspec
193
+ - example/CHANGELOG.md
179
194
  - example/README.md
180
195
  - example/config/receiver.json
181
196
  - example/config/router.json
@@ -189,11 +204,17 @@ files:
189
204
  - lib/eventhub/actor_listener_http.rb
190
205
  - lib/eventhub/actor_publisher.rb
191
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
192
210
  - lib/eventhub/base.rb
193
211
  - lib/eventhub/base_exception.rb
194
212
  - lib/eventhub/configuration.rb
195
213
  - lib/eventhub/constant.rb
196
214
  - lib/eventhub/consumer.rb
215
+ - lib/eventhub/correlation_id.rb
216
+ - lib/eventhub/docs_renderer.rb
217
+ - lib/eventhub/execution_id.rb
197
218
  - lib/eventhub/hash_extensions.rb
198
219
  - lib/eventhub/helper.rb
199
220
  - lib/eventhub/logger.rb
@@ -201,6 +222,7 @@ files:
201
222
  - lib/eventhub/processor2.rb
202
223
  - lib/eventhub/sleeper.rb
203
224
  - lib/eventhub/statistics.rb
225
+ - lib/eventhub/templates/layout.erb
204
226
  - lib/eventhub/version.rb
205
227
  homepage: https://github.com/thomis/eventhub-processor2
206
228
  licenses:
@@ -220,7 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
242
  - !ruby/object:Gem::Version
221
243
  version: '0'
222
244
  requirements: []
223
- rubygems_version: 4.0.3
245
+ rubygems_version: 4.0.6
224
246
  specification_version: 4
225
247
  summary: Next generation gem to build ruby based eventhub processor
226
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@v6
19
-
20
- - name: Set up Ruby
21
- uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: '4.0'
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