stack-service-base 0.0.4 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 340d3036dfae55df21ac629aea2f177b890257e29e8d51d7c7ec975bff57ee0b
4
- data.tar.gz: 76ab84fb603376f3a8f29901c5987cd4bf664625dd6a9bd533f20e928716a775
3
+ metadata.gz: 7fc2da143ab2622432f7fce2f36bf7e99e4679ffcb110f7b2a0f4b44d762b26b
4
+ data.tar.gz: 0da55dfcbe8f1ea8cd89ea6625238e99ad607729a914aa0bbe3b45197a5615a3
5
5
  SHA512:
6
- metadata.gz: d99472369e7dae16f8cfd8526e785a6dee35727e4f7e7b48e81a3509a295c9712883c8060d177f76cb56022d42055868f1ce3ee7cabd27163635a66bb9154bb9
7
- data.tar.gz: 3d86d30f6fbcbcb06339d2cdfe03afbf51a58696177d88c4d7fc81e404454093ba0c4e00ef80ac228ebc41140dbf6569f6c405577df0c1ca126ea00cc24360d3
6
+ metadata.gz: e6a8a2321eef07de698ef008bc3f8cfe834484dda97692e2efa10a8d80fb1ad03c7b7f6906246545d0af9da444723ea49b8d910276393f2efb48b0fc82a9a9a0
7
+ data.tar.gz: 0c9b6ea5c45abd03e06b9ec132e02abc7746019722b9fbebb30f8f56bb70e24e48944c0597b8a8b1fa42ee937ce02ddad22dd9bc1742f2ae195ed926b7cce113
@@ -0,0 +1,141 @@
1
+ require 'console'
2
+ require 'fiber'
3
+
4
+ QUIET = ENV.fetch('CONSOLE_LEVEL', 'false') == 'true'
5
+ PERFORMANCE = ENV.fetch('PERFORMANCE', 'false') == 'true'
6
+
7
+ ENV['CONSOLE_LEVEL'] ||= 'all' unless QUIET || PERFORMANCE
8
+
9
+ CONSOLE_LOGGER = Class.new {
10
+ def <<(...) = Console.logger.info(...)
11
+ def info(...) = Console.logger.info(...)
12
+ def debug(...) = Console.logger.debug(...)
13
+ def debug1(...) = Console.logger.debug(...)
14
+ def debug2(...) = Console.logger.debug(...)
15
+ def debug3(...) = Console.logger.debug(...)
16
+ def warn(...) = Console.logger.warn(...)
17
+ def error(...) = Console.logger.error(...)
18
+ def fatal(...) = Console.logger.fatal(...)
19
+ }.new
20
+
21
+ if QUIET
22
+ ENV['CONSOLE_LEVEL'] = 'error'
23
+ LOGGER = CONSOLE_LOGGER
24
+
25
+ # LOGGER = Class.new {
26
+ # def <<(...) = nil
27
+ # def info(...) = nil
28
+ # def debug(...) = nil
29
+ # def debug1(...) = nil
30
+ # def debug2(...) = nil
31
+ # def debug3(...) = nil
32
+ # def warn(...) = nil
33
+ # def error(...) = nil
34
+ # def fatal(...) = nil
35
+ # }.new
36
+ else
37
+ $stdout.sync = true
38
+ $stderr.sync = true
39
+ # class Fiber
40
+ # alias_method :old_init, :initialize
41
+ # attr_reader :parent
42
+ #
43
+ # def initialize(&)
44
+ # @parent = Fiber.current
45
+ # old_init(&)
46
+ # end
47
+ #
48
+ # def parents
49
+ # list = [@parent]
50
+ # list << list.last.parent while list.last.respond_to?(:parent) && !list.last.parent.nil?
51
+ # list
52
+ # end
53
+ # end
54
+
55
+ # https://socketry.github.io/traces/guides/getting-started/index.html
56
+ # OpenTelemetry / Datadog
57
+ # https://socketry.github.io/console/guides/getting-started/index.html
58
+ # ENV['TRACES_BACKEND'] = 'traces/backend/console'
59
+ # ENV['CONSOLE_LEVEL'] = 'all'
60
+ # ENV['CONSOLE_OUTPUT'] = 'XTerm' # JSON,Text,XTerm,Default
61
+ # TRACE_METHODS = true
62
+ TRACE_METHODS ||= !PERFORMANCE unless defined? TRACE_METHODS
63
+ if TRACE_METHODS
64
+ trace = TracePoint.new(:call, :return, :b_call, :b_return) { |tp| # :thread_begin, :thread_end
65
+ call_stack = Thread.current[:call_stack] ||= {}
66
+ call_stack_fiber = call_stack[Fiber.current.__id__] ||= []
67
+ call_stack_fiber << [tp.defined_class, tp.method_id] if [:call, :b_call].include? tp.event
68
+ call_stack_fiber.pop if [:return, :b_return].include? tp.event
69
+ }
70
+ trace.enable
71
+ end
72
+ # the_method
73
+ # trace.disable
74
+ LOG_DEPTH ||= 10 unless defined? LOG_DEPTH
75
+ LOGGER = Class.new {
76
+ def initialize = @context_list ||= {}
77
+
78
+ def add_context(name)
79
+ task = Async::Task.current?
80
+ @context_list[task.__id__] = name
81
+ end
82
+
83
+ def find_context
84
+ return '' unless Thread.current[:async_task]
85
+
86
+ t_stack = [Async::Task.current]
87
+ t_stack << t_stack.last.parent while t_stack.last.parent
88
+ task = t_stack.find { @context_list[_1.__id__] }
89
+
90
+ task ? " [#{@context_list[task.__id__]}] " : ''
91
+ end
92
+
93
+ def <<(...) = do_log(:info, ...)
94
+
95
+ def info(...) = do_log(:info, ...)
96
+
97
+ def debug(...) = do_log(:debug, ...)
98
+
99
+ def debug1(...) = do_log(:debug1, ...)
100
+
101
+ def debug2(...) = do_log(:debug2, ...)
102
+
103
+ def debug3(...) = do_log(:debug3, ...)
104
+
105
+ def warn(...) = do_log(:warn, ...)
106
+
107
+ def error(...) = do_log(:error, ...)
108
+
109
+ def fatal(...) = do_log(:fatal, ...)
110
+
111
+ def do_log(name, *args)
112
+ debug_level = name[/(\d+)/, 1].to_i
113
+ unless debug_level > LOG_DEPTH
114
+ if TRACE_METHODS
115
+ call_stack = Thread.current[:call_stack] ||= {}
116
+ call_stack_fiber = call_stack[Fiber.current.__id__] ||= []
117
+ last = call_stack_fiber[-3] ? call_stack_fiber[-3].join('.').gsub('Class:', '').gsub(/[#<>]/, '') : ''
118
+ last += find_context
119
+ msg = "\e[33m#{last}:\e[0m \e[38;5;254m" + args.flatten.map(&:inspect).join(', ')
120
+ else
121
+ msg = args.flatten.map(&:inspect).join(', ')
122
+ end
123
+ _name = name.to_s.gsub(/\d/, '')
124
+ _name = 'info' if _name == '<<'
125
+ Console.logger.send _name.to_s.gsub(/\d/, ''), msg
126
+ end
127
+ end
128
+ }.new
129
+
130
+ end
131
+
132
+ LOGGER_GRAPE = Class.new {
133
+ def method_missing(name, d)
134
+ Console.logger.send name, "REST_API: #{d[:method]} #{d[:path]} #{d[:params]} - #{d[:status]} host:#{d[:host]} time:#{d[:time]}"
135
+ end
136
+ }.new
137
+
138
+ $stdout.puts "QUIET: #{QUIET}"
139
+ $stdout.puts "PERFORMANCE: #{PERFORMANCE}"
140
+
141
+ ENV.select{ |k,v| k =~ /CONSOLE_/}.each { |k,v| $stdout.puts "#{k}: #{v}"}
@@ -52,8 +52,8 @@ if defined? LOGGER and OTEL_ENABLED
52
52
  OpenTelemetry.logger = LOGGER
53
53
  end
54
54
 
55
- def otel_initialize
56
- $stderr.puts "otl_configure: OTEL_ENABLED: #{OTEL_ENABLED}"
55
+ def otel_initialize(app)
56
+ $stdout.puts "otl_configure: OTEL_ENABLED: #{OTEL_ENABLED}"
57
57
  return unless OTEL_ENABLED
58
58
 
59
59
  OpenTelemetry::SDK.configure do |c|
@@ -89,6 +89,7 @@ def otel_initialize
89
89
  # span.status = OpenTelemetry::Trace::Status.error("error message here!")
90
90
  end
91
91
 
92
+ app.use OTELTraceInfo
92
93
  end
93
94
 
94
95
  if defined? Sequel and OTEL_ENABLED
@@ -0,0 +1,199 @@
1
+ module RackHelpers
2
+ def Rack.middleware_klass(&block)
3
+ Class.new do
4
+ define_method(:initialize) do |app, *opts, &block2|; @app = app; @opts = opts; @block = block2 || block end
5
+ def call(env) = @block.call env, @app, @opts
6
+ end
7
+ end
8
+
9
+ def Rack.define_middleware(name, &block)
10
+ Object.const_set name, Rack.middleware_klass(&block)
11
+ end
12
+
13
+ Rack.define_middleware :Authentication do |env, app|
14
+ token = env['HTTP_AUTHORIZATION'][/Bearer (.*)/, 1] rescue nil # Authorization: Bearer token
15
+ token ||= env['HTTP_AUTH'][/Bearer (.*)/, 1] rescue nil # AUTH: Bearer token
16
+ token ||= Rack::Request.new(env).cookies['token']
17
+ token_h = JWT.decode token, '', false, algorithm: 'RS256' rescue nil
18
+ token_h ||= [{}]
19
+
20
+ if defined? OpenTelemetry::Trace
21
+ OpenTelemetry::Trace.current_span.add_attributes'TOKEN' => token_h[0].to_json,
22
+ 'username' => (token_h[0]['username'] || '')
23
+ end
24
+ Async::Task.current.define_singleton_method :token, &-> { token_h[0] }
25
+
26
+ app.call env
27
+ end
28
+
29
+ # TODO:
30
+ # - Only required for cross-thread context transfer
31
+ #
32
+ # Rack.define_middleware :AsyncDBConnectionOT do |env, app|
33
+ # context_ = OpenTelemetry::Context.current
34
+ # OpenTelemetry::Context.with_current(context_) do
35
+ # OpenTelemetry::Trace.with_span(OpenTelemetry::Trace.current_span) do
36
+ # app.call env
37
+ # end
38
+ # end
39
+ # end
40
+
41
+ # Rack.define_middleware :SwaggerUI do |env, app, opts|
42
+ # # url = opts[:swagger]
43
+ # @static ||= Rack::Static.new app, urls: [''], root: "#{__dir__}/../swagger_ui/", index: 'index.html', cascade: true
44
+ # env['REQUEST_METHOD'] == 'GET' ? @static.call(env) : app.call(env)
45
+ # end
46
+
47
+ #
48
+
49
+ Rack.define_middleware :OTELTraceInfo do |env, app, opts|
50
+ status, headers, body = app.call env
51
+ if status.to_i >= 500
52
+ otl_current_span{
53
+ span_context = OpenTelemetry::Trace.current_span.context
54
+ trace_id = span_context.trace_id.unpack1('H*')
55
+
56
+ begin
57
+ bj = JSON.parse(body.join)
58
+ bj[:trace_id] = trace_id
59
+ body = [bj.to_json]
60
+ rescue =>e
61
+ body = [body.join + "\ntrace_id: #{trace_id}"]
62
+ end
63
+ }
64
+ end
65
+
66
+ [status, headers, body]
67
+ end
68
+
69
+ Rack.define_middleware :NoCache do |env, app, opts|
70
+ status, headers, body = app.call env
71
+ headers['Cache-Control'] = 'private,max-age=0,must-revalidate,no-store'
72
+ [status, headers, body]
73
+ end
74
+
75
+ Rack.define_middleware :RequestsLimiter do |env, app, opts|
76
+ @sem ||= Async::Semaphore.new(opts[0][:limit] || 5 )
77
+
78
+ if @sem.blocking?
79
+ [429, { 'Content-Type' => 'text/plain', 'Retry-After' => '1' }, ['Too Many Requests']]
80
+ else
81
+ @sem.acquire {
82
+ app.call(env)
83
+ }
84
+ end
85
+ end
86
+
87
+ # PATCH: for the Grape
88
+ class Rack::Lint::Wrapper::InputWrapper
89
+ def rewind = @input.rewind
90
+ end if defined? Rack::Lint::Wrapper::InputWrapper
91
+
92
+ class Rack::Lint
93
+ def call(env = nil) = @app.call(env)
94
+ end if defined? Rack::Lint
95
+
96
+ class Rack::CommonLogger
97
+ def log(env, status, header, began_at) = ()
98
+ end
99
+ # require 'rack/request'
100
+ # # sinatra was resolved to 3.0.2, which depends on
101
+ # # rack (~> 2.2, >= 2.2.4)
102
+ # # /home/user/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rack-2.2.4/lib/rack/request.rb
103
+ # module Rack::Request::Helpers
104
+ # alias old_post POST
105
+ # def POST = get_header(Rack::RACK_INPUT).empty? ? {} : old_post
106
+ # end
107
+
108
+ Rack.define_middleware :HeadersLogger do |env, app, opts|
109
+ LOGGER.info env.select { _1 =~ /HTTP/ }.transform_keys { _1.gsub 'HTTP_', '' }
110
+ status, headers, body =app.call env
111
+
112
+ if status.to_i / 100 == 5
113
+ _body = body.to_s # each(&:to_s).join
114
+ LOGGER.error [status, headers, _body]
115
+ if defined? OpenTelemetry::Trace
116
+ OpenTelemetry::Trace.current_span.tap do |span|
117
+ event_attributes = { 'exception.type' => "HTTP #{status.to_i}", 'exception.message' => _body, 'exception.stacktrace' => '' }
118
+ span.add_event('exception', attributes: event_attributes)
119
+ span.status = OpenTelemetry::Trace::Status.error("Request error: #{status.to_i}")
120
+ end
121
+ end
122
+ end
123
+
124
+ LOGGER.info headers
125
+ [status, headers, body]
126
+ end
127
+
128
+ Rack.define_middleware :RequestProfile do |env, app, opts|
129
+ start = Time.now
130
+ LOGGER.debug "start:#{start}"
131
+ status, headers, body = app.call env
132
+ LOGGER.debug "#{Time.now - start} sec - end (fiber id: #{Fiber.current.__id__}, async task: #{Async::Task.current})"
133
+ headers['x-runtime'] = "#{Time.now - start}"
134
+ # if headers['Content-Type'] =~ /html/
135
+ # headers['Set-Cookie'] = "Runtime=#{Time.now - start} sec;expires=Sat, 01-Jan-3000 00:00:00 GMT;path=/;"
136
+ # end
137
+ [status, headers, body]
138
+ end
139
+
140
+ class << self
141
+ def rack_setup(app)
142
+ # PATCH: for the Grape Swagger
143
+ # https://github.com/ruby-grape/grape-swagger/pull/905
144
+ # https://github.com/ruby-grape/grape-swagger/issues/904
145
+ GrapeSwagger::DocMethods::ParseParams.instance_eval do
146
+ def parse_enum_or_range_values(values)
147
+ case values
148
+ when Proc
149
+ parse_enum_or_range_values(values.call) if values.parameters.empty?
150
+ when Range
151
+ parse_range_values(values) # if values.first.is_a?(Integer) TODO
152
+ when Array
153
+ { enum: values }
154
+ else
155
+ { enum: [values] } if values
156
+ end
157
+ end
158
+ end if defined? GrapeSwagger::DocMethods::ParseParams
159
+
160
+ app.use Rack.middleware_klass do |env, app|
161
+ # env['REQUEST_PATH'] == '/healthcheck' ? [200, {}, ['Healthy']] : app.call(env)
162
+ env['PATH_INFO'] == '/healthcheck' ? [200, {'Content-Type' =>'application/json'}, [{ Status: 'Healthy' }.to_json ]] : app.call(env)
163
+ end
164
+
165
+ if defined? OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware
166
+ use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware
167
+ end
168
+ app.use Rack::Deflater
169
+ app.use OTELTraceInfo
170
+
171
+ unless defined?(PERFORMANCE) && PERFORMANCE
172
+ app.use RequestProfile
173
+ app.use HeadersLogger
174
+
175
+ if defined? Rack::ODataCommonLogger
176
+ app.use Rack::ODataCommonLogger, LOGGER # or use Rack::CommonLogger, LOGGER
177
+ end
178
+ end
179
+
180
+ app.use Rack.middleware_klass do |env, app|
181
+ code, headers, body = env['REQUEST_METHOD'] == 'OPTIONS' ? [200, {}, []] : app.call(env)
182
+
183
+ # scheme = env['rack.url_scheme']
184
+ # referer = URI.parse env['HTTP_REFERER']
185
+ headers.merge!(
186
+ # 'Access-Control-Allow-Origin' => "#{referer.scheme}://#{referer.host}",
187
+ 'Access-Control-Allow-Origin' => '*',
188
+ 'Access-Control-Allow-Methods' => 'GET, PUT, POST, DELETE, HEAD, OPTIONS',
189
+ 'Access-Control-Allow-Headers' => '*',
190
+ 'Access-Control-Allow-Credentials' => 'true')
191
+ [code, headers, body]
192
+ end
193
+
194
+ # unless @run
195
+ # run ->(_env) { [200, {'Content-Type' => 'text/plain'}, ['OK!?']] }
196
+ # end
197
+ end
198
+ end
199
+ end
@@ -1,3 +1,5 @@
1
+ require 'stack-service-base/logging'
2
+ require 'stack-service-base/rack_helpers'
1
3
  require 'stack-service-base/open_telemetry'
2
4
 
3
5
  module StackServiceBase
@@ -8,6 +10,7 @@ module StackServiceBase
8
10
  return unless app.respond_to? :use
9
11
 
10
12
  app.instance_eval do
13
+ RackHelpers.rack_setup app
11
14
  if ENV.fetch('PROMETHEUS_METRICS_EXPORT', 'true') == 'true'
12
15
  require 'stack-service-base/prometheus'
13
16
 
@@ -17,8 +20,7 @@ module StackServiceBase
17
20
  end
18
21
 
19
22
  if OTEL_ENABLED
20
- otel_initialize
21
-
23
+ otel_initialize app
22
24
  end
23
25
 
24
26
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module StackServiceBase
2
2
  class Base
3
- VERSION = '0.0.4'
3
+ VERSION = '0.0.6'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stack-service-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-05 00:00:00.000000000 Z
11
+ date: 2025-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prometheus-client
@@ -157,9 +157,11 @@ extensions: []
157
157
  extra_rdoc_files: []
158
158
  files:
159
159
  - lib/stack-service-base.rb
160
+ - lib/stack-service-base/logging.rb
160
161
  - lib/stack-service-base/open_telemetry.rb
161
162
  - lib/stack-service-base/prometheus.rb
162
163
  - lib/stack-service-base/prometheus_parser.rb
164
+ - lib/stack-service-base/rack_helpers.rb
163
165
  - lib/version.rb
164
166
  homepage:
165
167
  licenses: []