kirei 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -7
- data/kirei.gemspec +1 -0
- data/lib/cli/commands/new_app/execute.rb +1 -1
- data/lib/cli/commands/new_app/files/app.rb +9 -2
- data/lib/cli/commands/new_app/files/rakefile.rb +3 -0
- data/lib/cli/commands/new_app/files/routes.rb +2 -2
- data/lib/cli/commands/start.rb +1 -1
- data/lib/kirei/app.rb +3 -0
- data/lib/kirei/config.rb +4 -1
- data/lib/kirei/errors/json_api_error.rb +25 -0
- data/lib/kirei/errors/json_api_error_source.rb +12 -0
- data/lib/kirei/logging/level.rb +33 -0
- data/lib/kirei/logging/logger.rb +198 -0
- data/lib/kirei/logging/metric.rb +40 -0
- data/lib/kirei/model/base_class_interface.rb +55 -0
- data/lib/kirei/model/class_methods.rb +157 -0
- data/lib/kirei/model/human_id_generator.rb +40 -0
- data/lib/kirei/model.rb +4 -175
- data/lib/kirei/routing/base.rb +53 -13
- data/lib/kirei/routing/route.rb +13 -0
- data/lib/kirei/routing/router.rb +1 -31
- data/lib/kirei/routing/verb.rb +37 -0
- data/lib/kirei/services/result.rb +53 -0
- data/lib/kirei/services/runner.rb +54 -0
- data/lib/kirei/version.rb +1 -1
- data/lib/kirei.rb +5 -2
- data/lib/tasks/routes.rake +26 -0
- data/sorbet/rbi/shims/ruby.rbi +15 -0
- metadata +30 -3
- data/lib/kirei/logger.rb +0 -196
@@ -0,0 +1,54 @@
|
|
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: T.untyped,
|
14
|
+
log_tags: T::Hash[String, T.untyped],
|
15
|
+
block: T.proc.returns(T.type_parameter(:T)),
|
16
|
+
).returns(T.type_parameter(:T))
|
17
|
+
end
|
18
|
+
def self.call(class_name, log_tags: {}, &block)
|
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.to_s,
|
39
|
+
"service.latency_in_ms" => latency_in_ms,
|
40
|
+
"service.result" => result,
|
41
|
+
"service.source_location" => source_location(block),
|
42
|
+
}
|
43
|
+
logtags.merge!(log_tags)
|
44
|
+
|
45
|
+
Logging::Logger.call(level: Logging::Level::INFO, label: "Service Finished", meta: logtags)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { params(proc: T.proc.returns(T.untyped)).returns(String) }
|
49
|
+
private_class_method def self.source_location(proc)
|
50
|
+
proc.source_location.join(":").gsub(App.root.to_s, "")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/kirei/version.rb
CHANGED
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
|
-
|
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,26 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative("../kirei")
|
5
|
+
|
6
|
+
namespace :kirei do
|
7
|
+
desc "Prints all available routes"
|
8
|
+
task :routes do
|
9
|
+
router = Kirei::Routing::Router.instance
|
10
|
+
|
11
|
+
longest_path = router.routes.keys.map(&:length).max
|
12
|
+
|
13
|
+
routes_by_controller = router.routes.values.group_by(&:controller)
|
14
|
+
|
15
|
+
puts "\n"
|
16
|
+
|
17
|
+
routes_by_controller.each do |controller, routes|
|
18
|
+
puts "#{controller}:"
|
19
|
+
routes.each do |route|
|
20
|
+
verb = route.verb.serialize.upcase.ljust(7 + 3) # 7 is the length of the longest verb
|
21
|
+
puts "#{verb} #{route.path.ljust(longest_path + 1)} => ##{route.action}"
|
22
|
+
end
|
23
|
+
puts "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -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.
|
4
|
+
version: 0.4.1
|
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-
|
11
|
+
date: 2024-06-10 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,29 @@ 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/
|
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
|
206
|
+
- lib/tasks/routes.rake
|
181
207
|
- sorbet/rbi/shims/base_model.rbi
|
208
|
+
- sorbet/rbi/shims/ruby.rbi
|
182
209
|
homepage: https://github.com/swiknaba/kirei
|
183
210
|
licenses:
|
184
211
|
- 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
|