kirei 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Kirei
5
+ module Services
6
+ class Runner
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ sig do
11
+ type_parameters(:T)
12
+ .params(
13
+ class_name: String,
14
+ log_tags: T::Hash[String, T.untyped],
15
+ _: T.proc.returns(T.type_parameter(:T)),
16
+ ).returns(T.type_parameter(:T))
17
+ end
18
+ def self.call(class_name, log_tags: {}, &_)
19
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
20
+ service = yield
21
+
22
+ service
23
+ ensure
24
+ stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
25
+ latency_in_ms = stop - T.must(start)
26
+
27
+ result = case service
28
+ when Services::Result
29
+ service.success? ? "success" : "failure"
30
+ else
31
+ "unknown"
32
+ end
33
+
34
+ metric_tags = Logging::Metric.inject_defaults({ "service.result" => result })
35
+ ::StatsD.measure(class_name, latency_in_ms, tags: metric_tags)
36
+
37
+ logtags = {
38
+ "service.name" => class_name,
39
+ "service.latency_in_ms" => latency_in_ms,
40
+ "service.result" => result,
41
+ }
42
+ logtags.merge!(log_tags)
43
+ Logging::Logger.call(level: Logging::Level::INFO, label: "Service Finished", meta: logtags)
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/kirei/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Kirei
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
data/lib/kirei.rb CHANGED
@@ -13,6 +13,7 @@ require "bundler/setup"
13
13
 
14
14
  # Second: load all gems (runtime dependencies only)
15
15
  require "logger"
16
+ require "statsd-instrument"
16
17
  require "sorbet-runtime"
17
18
  require "oj"
18
19
  require "rack"
@@ -33,7 +34,7 @@ module Kirei
33
34
  # rubocop:disable Style/MutableConstant
34
35
  OJ_OPTIONS = T.let(
35
36
  {
36
- mode: :compat, # required to dump hashes with symbol-keys
37
+ mode: :compat, # required to dump hashes with symbol-keys. @TODO(lud, 14.05.2024): drop this, and enforce String Keys?
37
38
  symbol_keys: false, # T::Struct.new works only with string-keys
38
39
  },
39
40
  T::Hash[Symbol, T.untyped],
@@ -67,4 +68,6 @@ loader.eager_load
67
68
 
68
69
  Kirei.configure(&:itself)
69
70
 
70
- Kirei::Logger.logger.info("Kirei (v#{Kirei::VERSION}) booted.")
71
+ yjit_enabled = defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : false
72
+
73
+ Kirei::Logging::Logger.logger.info("Kirei v#{Kirei::VERSION} booted; YJIT enabled: #{yjit_enabled}")
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+
3
+ #
4
+ # The RubyVM module only exists on MRI. RubyVM is not defined in other Ruby implementations such as JRuby and TruffleRuby.
5
+ #
6
+ # The RubyVM module provides some access to MRI internals. This module is for very limited purposes, such as debugging, prototyping, and research. Normal users must not use it. This module is not portable between Ruby implementations.
7
+ #
8
+ class RubyVM
9
+ module YJIT
10
+ extend T::Sig
11
+
12
+ sig { returns(T::Boolean) }
13
+ def self.enabled?; end
14
+ end
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kirei
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ludwig Reinmiedl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-13 00:00:00.000000000 Z
11
+ date: 2024-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: statsd-instrument
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: tzinfo-data
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -169,16 +183,28 @@ files:
169
183
  - lib/kirei/app.rb
170
184
  - lib/kirei/config.rb
171
185
  - lib/kirei/controller.rb
186
+ - lib/kirei/errors/json_api_error.rb
187
+ - lib/kirei/errors/json_api_error_source.rb
172
188
  - lib/kirei/helpers.rb
173
- - lib/kirei/logger.rb
189
+ - lib/kirei/logging/level.rb
190
+ - lib/kirei/logging/logger.rb
191
+ - lib/kirei/logging/metric.rb
174
192
  - lib/kirei/model.rb
193
+ - lib/kirei/model/base_class_interface.rb
194
+ - lib/kirei/model/class_methods.rb
195
+ - lib/kirei/model/human_id_generator.rb
175
196
  - lib/kirei/routing/base.rb
176
197
  - lib/kirei/routing/nilable_hooks_type.rb
177
198
  - lib/kirei/routing/rack_env_type.rb
178
199
  - lib/kirei/routing/rack_response_type.rb
200
+ - lib/kirei/routing/route.rb
179
201
  - lib/kirei/routing/router.rb
202
+ - lib/kirei/routing/verb.rb
203
+ - lib/kirei/services/result.rb
204
+ - lib/kirei/services/runner.rb
180
205
  - lib/kirei/version.rb
181
206
  - sorbet/rbi/shims/base_model.rbi
207
+ - sorbet/rbi/shims/ruby.rbi
182
208
  homepage: https://github.com/swiknaba/kirei
183
209
  licenses:
184
210
  - MIT
data/lib/kirei/logger.rb DELETED
@@ -1,196 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module Kirei
5
- # rubocop:disable Metrics
6
-
7
- #
8
- # Example Usage:
9
- #
10
- # Kirei::Logger.call(
11
- # level: :info,
12
- # label: "Request started",
13
- # meta: {
14
- # key: "value",
15
- # },
16
- # )
17
- #
18
- # You can define a custom log transformer to transform the logline:
19
- #
20
- # Kirei::App.config.log_transformer = Proc.new { _1 }
21
- #
22
- # By default, "meta" is flattened, and sensitive values are masked using see `Kirei::App.config.sensitive_keys`.
23
- # You can also build on top of the provided log transformer:
24
- #
25
- # Kirei::App.config.log_transformer = Proc.new do |meta|
26
- # flattened_meta = Kirei::Logger.flatten_hash_and_mask_sensitive_values(meta)
27
- # # Do something with the flattened meta
28
- # flattened_meta.map { _1.to_json }
29
- # end
30
- #
31
- # NOTE: The log transformer must return an array of strings to allow emitting multiple lines per log event.
32
- #
33
- class Logger
34
- extend T::Sig
35
-
36
- FILTERED = "[FILTERED]"
37
-
38
- @instance = T.let(nil, T.nilable(Kirei::Logger))
39
-
40
- sig { void }
41
- def initialize
42
- super
43
- @queue = T.let(Thread::Queue.new, Thread::Queue)
44
- @thread = T.let(start_logging_thread, Thread)
45
- end
46
-
47
- sig { returns(Kirei::Logger) }
48
- def self.instance
49
- @instance ||= new
50
- end
51
-
52
- sig { returns(::Logger) }
53
- def self.logger
54
- return @logger unless @logger.nil?
55
-
56
- @logger = T.let(nil, T.nilable(::Logger))
57
- @logger ||= ::Logger.new($stdout)
58
-
59
- # we want the logline to be parseable to JSON
60
- @logger.formatter = proc do |_severity, _datetime, _progname, msg|
61
- "#{msg}\n"
62
- end
63
-
64
- @logger
65
- end
66
-
67
- sig do
68
- params(
69
- level: T.any(String, Symbol),
70
- label: String,
71
- meta: T::Hash[Symbol, T.untyped],
72
- ).void
73
- end
74
- def self.call(level:, label:, meta: {})
75
- return if ENV["LOGGER"] == "disabled"
76
-
77
- instance.call(level: level, label: label, meta: meta)
78
- end
79
-
80
- sig do
81
- params(
82
- level: T.any(String, Symbol),
83
- label: String,
84
- meta: T::Hash[Symbol, T.untyped],
85
- ).void
86
- end
87
- def call(level:, label:, meta: {})
88
- Kirei::App.config.log_default_metadata.each_pair do |key, value|
89
- meta[key] ||= value
90
- end
91
-
92
- #
93
- # key names follow OpenTelemetry Semantic Conventions
94
- # Source: https://opentelemetry.io/docs/concepts/semantic-conventions/
95
- #
96
- meta[:"service.instance.id"] ||= Thread.current[:request_id]
97
- meta[:"service.name"] ||= Kirei::App.config.app_name
98
-
99
- # The Ruby logger only accepts one string as the only argument
100
- @queue << { level: level, label: label, meta: meta }
101
- end
102
-
103
- sig { returns(Thread) }
104
- def start_logging_thread
105
- Thread.new do
106
- Kernel.loop do
107
- log_data = T.let(@queue.pop, T::Hash[Symbol, T.untyped])
108
- level = log_data.fetch(:level)
109
- label = log_data.fetch(:label)
110
- meta = T.let(log_data.fetch(:meta), T::Hash[Symbol, T.untyped])
111
- meta[:"service.version"] ||= Kirei::App.version
112
- meta[:timestamp] ||= Time.now.utc.iso8601
113
- meta[:level] ||= level.to_s.upcase
114
- meta[:label] ||= label
115
-
116
- log_transformer = App.config.log_transformer
117
-
118
- loglines = if log_transformer
119
- log_transformer.call(meta)
120
- else
121
- [Oj.dump(
122
- Kirei::Logger.flatten_hash_and_mask_sensitive_values(meta),
123
- Kirei::OJ_OPTIONS,
124
- )]
125
- end
126
-
127
- loglines.each { Kirei::Logger.logger.error(_1) }
128
- end
129
- end
130
- end
131
-
132
- # rubocop:disable Naming/MethodParameterName
133
- sig do
134
- params(
135
- k: Symbol,
136
- v: String,
137
- ).returns(String)
138
- end
139
- def self.mask(k, v)
140
- return Kirei::Logger::FILTERED if App.config.sensitive_keys.any? { k.match?(_1) }
141
-
142
- v
143
- end
144
- # rubocop:enable Naming/MethodParameterName
145
-
146
- sig do
147
- params(
148
- hash: T::Hash[T.any(Symbol, String), T.untyped],
149
- prefix: Symbol,
150
- ).returns(T::Hash[Symbol, T.untyped])
151
- end
152
- def self.flatten_hash_and_mask_sensitive_values(hash, prefix = :'')
153
- result = T.let({}, T::Hash[Symbol, T.untyped])
154
- Kirei::Helpers.deep_symbolize_keys!(hash)
155
- hash = T.cast(hash, T::Hash[Symbol, T.untyped])
156
-
157
- hash.each do |key, value|
158
- new_prefix = Kirei::Helpers.blank?(prefix) ? key : :"#{prefix}.#{key}"
159
-
160
- case value
161
- when Hash
162
- # Some libraries have a custom Hash class that inhert from Hash, but act differently, e.g. OmniAuth::AuthHash.
163
- # This results in `transform_keys` being available but without any effect.
164
- value = value.to_h if value.class != Hash
165
- result.merge!(flatten_hash_and_mask_sensitive_values(value.transform_keys(&:to_sym), new_prefix))
166
- when Array
167
- value.each_with_index do |element, index|
168
- if element.is_a?(Hash) || element.is_a?(Array)
169
- result.merge!(flatten_hash_and_mask_sensitive_values({ index => element }, new_prefix))
170
- else
171
- result[:"#{new_prefix}.#{index}"] = element.is_a?(String) ? mask(key, element) : element
172
- end
173
- end
174
- when String then result[new_prefix] = mask(key, value)
175
- when Numeric, FalseClass, TrueClass, NilClass then result[new_prefix] = value
176
- else
177
- if value.respond_to?(:serialize)
178
- serialized_value = value.serialize
179
- if serialized_value.is_a?(Hash)
180
- result.merge!(
181
- flatten_hash_and_mask_sensitive_values(serialized_value.transform_keys(&:to_sym), new_prefix),
182
- )
183
- else
184
- result[new_prefix] = serialized_value&.to_s
185
- end
186
- else
187
- result[new_prefix] = value&.to_s
188
- end
189
- end
190
- end
191
-
192
- result
193
- end
194
- end
195
- # rubocop:enable Metrics
196
- end