aliyun-log 0.2.6 → 0.2.12
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 +4 -4
- data/lib/aliyun/log.rb +1 -0
- data/lib/aliyun/log/config.rb +2 -1
- data/lib/aliyun/log/record.rb +3 -37
- data/lib/aliyun/log/record/exception.rb +1 -0
- data/lib/aliyun/log/record/field.rb +10 -6
- data/lib/aliyun/log/record/persistence.rb +38 -19
- data/lib/aliyun/log/record/relation.rb +238 -27
- data/lib/aliyun/log/record/scoping.rb +56 -0
- data/lib/aliyun/log/record/type_casting.rb +212 -0
- data/lib/aliyun/log/request.rb +10 -24
- data/lib/aliyun/log/utils.rb +29 -0
- data/lib/aliyun/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba1bcbd83277ca8da27978ecd7a603a205a19c1600b11f359135e2629e6f61c4
|
4
|
+
data.tar.gz: d1e731f6b181167ef11004c7c79bbbef9acd290be531a08a24bb6f87134cfaac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 850989116e4820287e15fbe5361319f1043d4a1c3e7991bab947187bcac2d0114ee9244df26c4c132cd29d400752b4f18a6c0a3bcccaaabdda151858829b19e4
|
7
|
+
data.tar.gz: c03afaddd81499c4cb36be357d9894eccf2574fa009afa2d57f7d01e61885534a60b94f4bc2dfaa315e5db0862481225966fc71f27fb8ce1d6a1686284f90e40
|
data/lib/aliyun/log.rb
CHANGED
data/lib/aliyun/log/config.rb
CHANGED
@@ -7,7 +7,8 @@ module Aliyun
|
|
7
7
|
@endpoint = 'https://cn-beijing.log.aliyuncs.com'
|
8
8
|
@open_timeout = 10
|
9
9
|
@read_timeout = 120
|
10
|
-
@log_level = Logger::
|
10
|
+
@log_level = Logger::WARN
|
11
|
+
@log_file = STDOUT
|
11
12
|
@timestamps = true
|
12
13
|
class << self
|
13
14
|
attr_accessor :endpoint, :access_key_id, :access_key_secret,
|
data/lib/aliyun/log/record.rb
CHANGED
@@ -9,8 +9,7 @@ require 'forwardable'
|
|
9
9
|
require_relative 'record/exception'
|
10
10
|
require_relative 'record/field'
|
11
11
|
require_relative 'record/persistence'
|
12
|
-
require_relative 'record/
|
13
|
-
require_relative 'record/scope_registry'
|
12
|
+
require_relative 'record/scoping'
|
14
13
|
|
15
14
|
module Aliyun
|
16
15
|
module Log
|
@@ -28,7 +27,7 @@ module Aliyun
|
|
28
27
|
|
29
28
|
Log.included_models << self unless Log.included_models.include? self
|
30
29
|
|
31
|
-
field :created_at, :text if Config.timestamps
|
30
|
+
field :created_at, type: :text, cast_type: :datetime if Config.timestamps
|
32
31
|
|
33
32
|
define_model_callbacks :save, :create, :initialize
|
34
33
|
|
@@ -37,6 +36,7 @@ module Aliyun
|
|
37
36
|
|
38
37
|
include Field
|
39
38
|
include Persistence
|
39
|
+
include Scoping
|
40
40
|
|
41
41
|
include ActiveModel::AttributeMethods
|
42
42
|
|
@@ -52,40 +52,6 @@ module Aliyun
|
|
52
52
|
opt[:field_doc_value] = opt[:field_doc_value] != false
|
53
53
|
self.options = opt
|
54
54
|
end
|
55
|
-
|
56
|
-
delegate :load, :result, :count, to: :all
|
57
|
-
delegate :where, :query, :search, :sql, :from, :to, :page, :line, :limit, :offset, to: :all
|
58
|
-
delegate :first, :last, :second, :third, :fourth, :fifth, :find_offset, to: :all
|
59
|
-
|
60
|
-
def current_scope
|
61
|
-
ScopeRegistry.value_for(:current_scope, self)
|
62
|
-
end
|
63
|
-
|
64
|
-
def current_scope=(scope)
|
65
|
-
ScopeRegistry.set_value_for(:current_scope, self, scope)
|
66
|
-
end
|
67
|
-
|
68
|
-
def scope(name, body)
|
69
|
-
raise ArgumentError, 'The scope body needs to be callable.' unless body.respond_to?(:call)
|
70
|
-
|
71
|
-
singleton_class.send(:define_method, name) do |*args|
|
72
|
-
scope = all
|
73
|
-
scope = scope.scoping { body.call(*args) }
|
74
|
-
scope
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def all
|
79
|
-
scope = current_scope
|
80
|
-
scope ||= relation.from(0).to(Time.now.to_i)
|
81
|
-
scope
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def relation
|
87
|
-
Relation.new(self)
|
88
|
-
end
|
89
55
|
end
|
90
56
|
|
91
57
|
def initialize(attrs = {})
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'type_casting'
|
4
|
+
|
3
5
|
module Aliyun
|
4
6
|
module Log
|
5
7
|
module Record
|
@@ -18,10 +20,12 @@ module Aliyun
|
|
18
20
|
|
19
21
|
included do
|
20
22
|
class_attribute :attributes, instance_accessor: false, default: {}
|
23
|
+
extend Common::Logging
|
21
24
|
end
|
22
25
|
|
23
26
|
module ClassMethods
|
24
|
-
def field(name,
|
27
|
+
def field(name, options = {})
|
28
|
+
type = options[:type] || :text
|
25
29
|
unless PERMITTED_KEY_TYPES.include?(type)
|
26
30
|
raise ArgumentError, "Field #{name} type(#{type}) error, key type only support text/long/double/json"
|
27
31
|
end
|
@@ -33,7 +37,7 @@ module Aliyun
|
|
33
37
|
warn_about_method_overriding("#{named}=", name)
|
34
38
|
warn_about_method_overriding("#{named}?", name)
|
35
39
|
|
36
|
-
define_attribute_method(name)
|
40
|
+
define_attribute_method(name)
|
37
41
|
|
38
42
|
generated_methods.module_eval do
|
39
43
|
define_method(named) { read_attribute(named) }
|
@@ -61,7 +65,6 @@ module Aliyun
|
|
61
65
|
remove_method field
|
62
66
|
remove_method :"#{field}="
|
63
67
|
remove_method :"#{field}?"
|
64
|
-
remove_method :"#{field}_before_type_cast"
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
@@ -77,7 +80,8 @@ module Aliyun
|
|
77
80
|
|
78
81
|
def warn_about_method_overriding(method_name, field_name)
|
79
82
|
if instance_methods.include?(method_name.to_sym)
|
80
|
-
|
83
|
+
logger.warn("Method #{method_name} generated for the field #{field_name} " \
|
84
|
+
'overrides already existing method')
|
81
85
|
end
|
82
86
|
end
|
83
87
|
end
|
@@ -103,7 +107,7 @@ module Aliyun
|
|
103
107
|
attr_accessor :attributes
|
104
108
|
|
105
109
|
def write_attribute(name, value)
|
106
|
-
attributes[name.to_sym] = value
|
110
|
+
attributes[name.to_sym] = TypeCasting.cast_field(value, self.class.attributes[name.to_sym])
|
107
111
|
end
|
108
112
|
|
109
113
|
alias []= write_attribute
|
@@ -114,7 +118,7 @@ module Aliyun
|
|
114
118
|
alias [] read_attribute
|
115
119
|
|
116
120
|
def set_created_at
|
117
|
-
self.created_at ||= DateTime.now.in_time_zone(Time.zone).
|
121
|
+
self.created_at ||= DateTime.now.in_time_zone(Time.zone).iso8601 if timestamps_enabled?
|
118
122
|
end
|
119
123
|
|
120
124
|
def timestamps_enabled?
|
@@ -35,6 +35,7 @@ module Aliyun
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def sync_index
|
38
|
+
return if field_indices.blank?
|
38
39
|
has_index? ? update_index : create_index
|
39
40
|
end
|
40
41
|
|
@@ -56,9 +57,8 @@ module Aliyun
|
|
56
57
|
def create(data, opts = {})
|
57
58
|
auto_load_schema
|
58
59
|
if data.is_a?(Array)
|
59
|
-
logs =
|
60
|
-
|
61
|
-
logs << new(log_attr).save_array
|
60
|
+
logs = data.map do |log_attr|
|
61
|
+
new(log_attr).save_array
|
62
62
|
end
|
63
63
|
res = Log.record_connection.put_log(project_name, logstore_name, logs, opts)
|
64
64
|
res.code == 200
|
@@ -80,17 +80,11 @@ module Aliyun
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def field_indices
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
indices.each do |_, v|
|
89
|
-
next unless v[:doc_value].nil?
|
90
|
-
|
91
|
-
v[:doc_value] = options[:field_doc_value] != false
|
83
|
+
if options[:field_index] == false
|
84
|
+
attributes.select { |_, value| value[:index] == true }
|
85
|
+
else
|
86
|
+
attributes.reject { |_, value| value[:index] == false }
|
92
87
|
end
|
93
|
-
indices
|
94
88
|
end
|
95
89
|
|
96
90
|
def create_index
|
@@ -104,18 +98,39 @@ module Aliyun
|
|
104
98
|
def update_index
|
105
99
|
conf_res = Log.record_connection.get_index(project_name, logstore_name)
|
106
100
|
raw_conf = JSON.parse(conf_res)
|
107
|
-
index_conf = raw_conf.
|
108
|
-
|
109
|
-
index_conf['keys']
|
101
|
+
index_conf = raw_conf.deep_dup
|
102
|
+
field_index_types.each do |k, v|
|
103
|
+
index_conf['keys'] ||= {}
|
104
|
+
index_conf['keys'][k.to_s] ||= {}
|
105
|
+
index_conf['keys'][k.to_s].merge!(v.as_json)
|
110
106
|
end
|
111
107
|
return if index_conf['keys'] == raw_conf['keys']
|
112
108
|
|
113
109
|
Log.record_connection.update_index(
|
114
110
|
project_name,
|
115
111
|
logstore_name,
|
116
|
-
index_conf['keys']
|
112
|
+
index_conf['keys'].with_indifferent_access
|
117
113
|
)
|
118
114
|
end
|
115
|
+
|
116
|
+
def field_index_types
|
117
|
+
field_indices.tap do |tap|
|
118
|
+
tap.each do |_, v|
|
119
|
+
v[:alias] ||= ''
|
120
|
+
v[:caseSensitive] ||= false
|
121
|
+
v[:chn] ||= false
|
122
|
+
v[:doc_value] = options[:field_doc_value] != false if v[:doc_value].nil?
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def dump_attributes
|
129
|
+
attributes.dup.tap do |tap|
|
130
|
+
tap.each do |k, v|
|
131
|
+
tap[k] = TypeCasting.dump_field(v, self.class.attributes[k])
|
132
|
+
end
|
133
|
+
end
|
119
134
|
end
|
120
135
|
|
121
136
|
def save
|
@@ -123,7 +138,11 @@ module Aliyun
|
|
123
138
|
run_callbacks(:create) do
|
124
139
|
run_callbacks(:save) do
|
125
140
|
if valid?
|
126
|
-
res = Log.record_connection.put_log(
|
141
|
+
res = Log.record_connection.put_log(
|
142
|
+
self.class.project_name,
|
143
|
+
self.class.logstore_name,
|
144
|
+
dump_attributes
|
145
|
+
)
|
127
146
|
res.code == 200
|
128
147
|
else
|
129
148
|
false
|
@@ -135,7 +154,7 @@ module Aliyun
|
|
135
154
|
def save_array
|
136
155
|
run_callbacks(:create) do
|
137
156
|
run_callbacks(:save) do
|
138
|
-
validate! &&
|
157
|
+
validate! && dump_attributes
|
139
158
|
end
|
140
159
|
end
|
141
160
|
end
|
@@ -8,7 +8,6 @@ module Aliyun
|
|
8
8
|
@klass = klass
|
9
9
|
@opts = opts
|
10
10
|
@klass.auto_load_schema
|
11
|
-
@opts[:search] ||= '*'
|
12
11
|
end
|
13
12
|
|
14
13
|
def inspect
|
@@ -36,14 +35,28 @@ module Aliyun
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def last(line = 1)
|
39
|
-
find_offset(0, line, true)
|
38
|
+
data = find_offset(0, line, true)
|
39
|
+
line > 1 ? data.reverse : data
|
40
40
|
end
|
41
41
|
|
42
42
|
def find_offset(nth, line = 1, reverse = false)
|
43
|
+
# @opts[:select] = '*'
|
43
44
|
@opts[:line] = line
|
44
45
|
@opts[:offset] = nth
|
45
|
-
@opts[:
|
46
|
-
|
46
|
+
if @opts[:order].present? && reverse
|
47
|
+
conds = []
|
48
|
+
@opts[:order].split(',').each do |field|
|
49
|
+
field.gsub!(/\s+desc|asc.*/i, '')
|
50
|
+
conds << "#{field.strip} DESC"
|
51
|
+
end
|
52
|
+
@opts[:order] = conds.join(', ')
|
53
|
+
end
|
54
|
+
@opts[:order] ||= reverse ? '__time__ DESC' : '__time__ ASC'
|
55
|
+
if @opts[:select].blank?
|
56
|
+
line <= 1 ? load[0] : load
|
57
|
+
else
|
58
|
+
line <= 1 ? result[0] : result
|
59
|
+
end
|
47
60
|
end
|
48
61
|
|
49
62
|
def scoping
|
@@ -79,21 +92,67 @@ module Aliyun
|
|
79
92
|
|
80
93
|
def page(val)
|
81
94
|
@opts[:page] = val - 1 if val >= 1
|
95
|
+
singleton_class.send(:define_method, :total_count) do
|
96
|
+
@total_count ||= count
|
97
|
+
end
|
98
|
+
singleton_class.send(:define_method, :total_pages) do
|
99
|
+
(total_count.to_f / (@opts[:line] || 100)).ceil
|
100
|
+
end
|
82
101
|
self
|
83
102
|
end
|
84
103
|
|
85
|
-
def
|
104
|
+
def api(opts)
|
86
105
|
@opts.merge!(opts)
|
87
106
|
self
|
88
107
|
end
|
89
108
|
|
90
|
-
def search(
|
91
|
-
|
109
|
+
def search(*statement)
|
110
|
+
ql = statement_ql(*statement)
|
111
|
+
@opts[:search] = ql if ql.present?
|
92
112
|
self
|
93
113
|
end
|
94
114
|
|
95
|
-
def sql(
|
96
|
-
|
115
|
+
def sql(*statement)
|
116
|
+
unless statement[0].is_a?(String)
|
117
|
+
raise ParseStatementInvalid, 'Only support string statement'
|
118
|
+
end
|
119
|
+
ql = sanitize_array(*statement)
|
120
|
+
@opts[:sql] = ql if ql.present?
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def where(*statement)
|
125
|
+
if statement[0].is_a?(String)
|
126
|
+
ql = sanitize_array(*statement)
|
127
|
+
@opts[:where] = ql
|
128
|
+
else
|
129
|
+
ql = statement_ql(*statement)
|
130
|
+
@opts[:search] = ql
|
131
|
+
end
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
def select(*fields)
|
136
|
+
@opts[:select] = fields.join(', ')
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def group(*fields)
|
141
|
+
@opts[:group] = fields.join(', ')
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
def order(*fields)
|
146
|
+
if fields[0].is_a?(Hash)
|
147
|
+
@opts[:order] = fields[0].map do |k, v|
|
148
|
+
unless %w[asc desc].include?(v.to_s)
|
149
|
+
raise ArgumentError, "Direction \"#{v}\" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC]"
|
150
|
+
end
|
151
|
+
"#{k} #{v}"
|
152
|
+
end.join(', ')
|
153
|
+
else
|
154
|
+
@opts[:order] = fields.join(', ')
|
155
|
+
end
|
97
156
|
self
|
98
157
|
end
|
99
158
|
|
@@ -104,40 +163,192 @@ module Aliyun
|
|
104
163
|
|
105
164
|
def count
|
106
165
|
query = @opts.dup
|
107
|
-
if query[:
|
108
|
-
|
109
|
-
|
110
|
-
|
166
|
+
if query[:select].blank?
|
167
|
+
@opts[:select] = 'COUNT(*) as count'
|
168
|
+
sql = to_sql
|
169
|
+
else
|
170
|
+
_sql = to_sql
|
171
|
+
sql = "SELECT COUNT(*) as count"
|
172
|
+
sql += " FROM(#{_sql})" if _sql.present?
|
111
173
|
end
|
112
|
-
|
113
|
-
res =
|
114
|
-
res
|
174
|
+
query[:query] = "#{query[:search] || '*'}|#{sql}"
|
175
|
+
res = execute(query)
|
176
|
+
res.dig(0, 'count').to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
def sum(field)
|
180
|
+
@opts[:select] = "SUM(#{field}) as sum"
|
181
|
+
query = @opts.dup
|
182
|
+
query[:query] = "#{query[:search] || '*'}|#{to_sql}"
|
183
|
+
res = execute(query)
|
184
|
+
res.dig(0, 'sum').to_f
|
115
185
|
end
|
116
186
|
|
117
187
|
def result
|
118
188
|
query = @opts.dup
|
119
|
-
|
120
|
-
|
121
|
-
|
189
|
+
query[:query] = query[:search] || '*'
|
190
|
+
sql = to_sql
|
191
|
+
if sql.present?
|
192
|
+
query[:query] += "|#{sql}"
|
193
|
+
@opts[:line] = nil
|
194
|
+
@opts[:offset] = nil
|
195
|
+
@opts[:page] = nil
|
122
196
|
end
|
123
|
-
query
|
124
|
-
query[:query] = "#{query[:query]}|#{query[:sql]}" if query[:sql].present?
|
125
|
-
res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
|
126
|
-
JSON.parse(res)
|
197
|
+
execute(query)
|
127
198
|
end
|
128
199
|
|
129
200
|
def load
|
130
201
|
result.map do |json_attr|
|
131
|
-
|
132
|
-
|
133
|
-
|
202
|
+
record = @klass.new
|
203
|
+
json_attr.each do |key, value|
|
204
|
+
record.send("#{key}=", value) if record.respond_to?("#{key}=")
|
134
205
|
end
|
135
|
-
|
206
|
+
record
|
136
207
|
end
|
137
208
|
end
|
138
209
|
|
210
|
+
def to_sql
|
211
|
+
return @opts[:sql] if @opts[:sql].present?
|
212
|
+
opts = @opts.dup
|
213
|
+
sql_query = []
|
214
|
+
sql_query << "WHERE #{opts[:where]}" if opts[:where].present?
|
215
|
+
sql_query << "GROUP BY #{opts[:group]}" if opts[:group].present?
|
216
|
+
sql_query << "ORDER BY #{opts[:order]}" if opts[:order].present?
|
217
|
+
if sql_query.present? || opts[:select].present?
|
218
|
+
sql_query.insert(0, "SELECT #{opts[:select] || '*'}")
|
219
|
+
sql_query.insert(1, 'FROM log')
|
220
|
+
if opts[:line] || opts[:page] || opts[:offset]
|
221
|
+
parse_page
|
222
|
+
sql_query << "LIMIT #{@opts[:offset]},#{@opts[:line]}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
"#{opts[:query]}#{sql_query.join(' ')}"
|
226
|
+
end
|
227
|
+
|
139
228
|
private
|
140
229
|
|
230
|
+
def execute(query)
|
231
|
+
puts query.slice(:from, :to, :search, :query).to_json
|
232
|
+
res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
|
233
|
+
JSON.parse(res)
|
234
|
+
end
|
235
|
+
|
236
|
+
def parse_page
|
237
|
+
@opts[:line] ||= 100
|
238
|
+
@opts[:page] ||= 0
|
239
|
+
@opts[:offset] = @opts[:page] * @opts[:line]
|
240
|
+
end
|
241
|
+
|
242
|
+
def statement_ql(*statement)
|
243
|
+
if statement.size == 1
|
244
|
+
sanitize_hash(statement.first)
|
245
|
+
elsif statement.size > 1
|
246
|
+
sanitize_array(*statement)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def sanitize_hash(search_content)
|
251
|
+
return search_content unless search_content.is_a?(Hash)
|
252
|
+
|
253
|
+
search_content.select { |_, v| v.present? }.map do |key, value|
|
254
|
+
options = @klass.attributes[:"#{key}"]
|
255
|
+
unless options
|
256
|
+
raise UnknownAttributeError, "unknown field '#{key}' for #{@klass.name}."
|
257
|
+
end
|
258
|
+
|
259
|
+
raise_if_hash_quote(value)
|
260
|
+
|
261
|
+
cast_type = options[:cast_type]
|
262
|
+
if value.is_a?(Array)
|
263
|
+
values = value.uniq.map { |v| _quote(cast_type, v) }
|
264
|
+
str_values = values.map { |v| "#{key}: #{v}" }.join(' OR ')
|
265
|
+
values.size > 1 ? "(#{str_values})" : str_values
|
266
|
+
elsif value.is_a?(Range)
|
267
|
+
"#{key} in [#{value.begin} #{value.end}]"
|
268
|
+
else
|
269
|
+
"#{key}: #{_quote(cast_type, value)}"
|
270
|
+
end
|
271
|
+
end.join(' AND ')
|
272
|
+
end
|
273
|
+
|
274
|
+
def sanitize_array(*ary)
|
275
|
+
statement, *values = ary
|
276
|
+
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
277
|
+
replace_named_bind_variables(statement, values.first)
|
278
|
+
elsif statement.include?('?')
|
279
|
+
replace_bind_variables(statement, values)
|
280
|
+
elsif statement.blank? || values.blank?
|
281
|
+
statement
|
282
|
+
else
|
283
|
+
statement % values.collect(&:to_s)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def replace_named_bind_variables(statement, bind_vars)
|
288
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
289
|
+
if bind_vars.include?(match = Regexp.last_match(2).to_sym)
|
290
|
+
match_value = bind_vars[match]
|
291
|
+
raise_if_hash_quote(match_value)
|
292
|
+
if match_value.is_a?(Array) || match_value.is_a?(Range)
|
293
|
+
values = match_value.map { |v| _quote_type_value(v) }
|
294
|
+
values.join(', ')
|
295
|
+
else
|
296
|
+
_quote_type_value(match_value)
|
297
|
+
end
|
298
|
+
else
|
299
|
+
raise ParseStatementInvalid, "missing value for :#{match} in #{statement}"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def replace_bind_variables(statement, values)
|
305
|
+
expected = statement.count('?')
|
306
|
+
provided = values.size
|
307
|
+
if expected != provided
|
308
|
+
raise ParseStatementInvalid, "wrong number of bind variables (#{provided} " \
|
309
|
+
"for #{expected}) in: #{statement}"
|
310
|
+
end
|
311
|
+
bound = values.dup
|
312
|
+
statement.gsub(/\?/) do
|
313
|
+
value = bound.shift
|
314
|
+
raise_if_hash_quote(value)
|
315
|
+
if value.is_a?(Array) || value.is_a?(Range)
|
316
|
+
values = value.map { |v| _quote_type_value(v) }
|
317
|
+
values.join(', ')
|
318
|
+
else
|
319
|
+
_quote_type_value(value)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def _quote(type, value)
|
325
|
+
v = TypeCasting.cast_field(value, cast_type: type || :string)
|
326
|
+
case type
|
327
|
+
when :string, nil then "'#{v.to_s}'"
|
328
|
+
when :bigdecimal then v.to_s("F")
|
329
|
+
when :integer then v.to_s.to_i
|
330
|
+
when :datetime, :date then "'#{v.iso8601}'"
|
331
|
+
else
|
332
|
+
value.to_s
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def _quote_type_value(value)
|
337
|
+
case value.class.name
|
338
|
+
when 'String' then "'#{value.to_s}'"
|
339
|
+
when 'BigDecimal', 'Float' then value.to_s("F")
|
340
|
+
when 'Date', 'DateTime', 'Time' then "'#{value.iso8601}'"
|
341
|
+
else
|
342
|
+
value
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def raise_if_hash_quote(value)
|
347
|
+
if value.is_a?(Hash) || value.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
348
|
+
raise ParseStatementInvalid, "can't quote Hash"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
141
352
|
def method_missing(method, *args, &block)
|
142
353
|
if @klass.respond_to?(method)
|
143
354
|
scoping { @klass.public_send(method, *args, &block) }
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'scope_registry'
|
4
|
+
require_relative 'relation'
|
5
|
+
|
6
|
+
module Aliyun
|
7
|
+
module Log
|
8
|
+
module Record
|
9
|
+
module Scoping
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
delegate :load, :result, :count, :sum, to: :all
|
14
|
+
delegate :where, :query, :search, :sql, :from, :to, :api,
|
15
|
+
:page, :line, :limit, :offset, :select, :group,
|
16
|
+
:order, :to_sql, to: :all
|
17
|
+
delegate :first, :last, :second, :third, :fourth, :fifth, :find_offset, to: :all
|
18
|
+
|
19
|
+
def current_scope
|
20
|
+
ScopeRegistry.value_for(:current_scope, self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def current_scope=(scope)
|
24
|
+
ScopeRegistry.set_value_for(:current_scope, self, scope)
|
25
|
+
end
|
26
|
+
|
27
|
+
def scope(name, body)
|
28
|
+
raise ArgumentError, 'The scope body needs to be callable.' unless body.respond_to?(:call)
|
29
|
+
|
30
|
+
singleton_class.send(:define_method, name) do |*args|
|
31
|
+
scope = all
|
32
|
+
scope = scope.scoping { body.call(*args) }
|
33
|
+
scope
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def unscoped
|
38
|
+
block_given? ? relation.scoping { yield } : relation
|
39
|
+
end
|
40
|
+
|
41
|
+
def all
|
42
|
+
scope = current_scope
|
43
|
+
scope ||= relation.from(0).to(Time.now.to_i)
|
44
|
+
scope
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def relation
|
50
|
+
Relation.new(self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Aliyun
|
6
|
+
module Log
|
7
|
+
module Record
|
8
|
+
module TypeCasting
|
9
|
+
TYPE_MAPPING = {
|
10
|
+
text: :string,
|
11
|
+
long: :integer,
|
12
|
+
double: :float,
|
13
|
+
json: :json
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def self.cast_field(value, options)
|
17
|
+
options ||= {}
|
18
|
+
type = options[:cast_type]
|
19
|
+
type ||= TYPE_MAPPING[options[:type]]
|
20
|
+
|
21
|
+
return value if options.nil?
|
22
|
+
return nil if value.nil?
|
23
|
+
|
24
|
+
caster = Registry.lookup(type)
|
25
|
+
raise ArgumentError, "Unknown type #{options[:type]}" if caster.nil?
|
26
|
+
|
27
|
+
caster.new(options).cast(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.dump_field(value, options)
|
31
|
+
options ||= {}
|
32
|
+
type = options[:cast_type]
|
33
|
+
type ||= TYPE_MAPPING[options[:type]]
|
34
|
+
|
35
|
+
return value if options.nil?
|
36
|
+
return nil if value.nil?
|
37
|
+
|
38
|
+
dumper = Registry.lookup(type)
|
39
|
+
raise ArgumentError, "Unknown type #{options[:type]}" if dumper.nil?
|
40
|
+
|
41
|
+
dumper.new(options).dump(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
class Value
|
45
|
+
def initialize(options)
|
46
|
+
@options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def cast(value)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def dump(value)
|
54
|
+
value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class StringType < Value; end
|
59
|
+
|
60
|
+
class DateType < Value
|
61
|
+
def cast(value)
|
62
|
+
return nil unless value.respond_to?(:to_date)
|
63
|
+
|
64
|
+
value.to_date
|
65
|
+
end
|
66
|
+
|
67
|
+
def dump(value)
|
68
|
+
if value.respond_to?(:to_date)
|
69
|
+
value.to_date.to_s
|
70
|
+
else
|
71
|
+
value.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class DateTimeType < Value
|
77
|
+
def cast(value)
|
78
|
+
return nil unless value.respond_to?(:to_datetime)
|
79
|
+
|
80
|
+
dt = begin
|
81
|
+
::DateTime.parse(value)
|
82
|
+
rescue StandardError
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
if dt
|
86
|
+
seconds = string_utc_offset(value) || 0
|
87
|
+
offset = seconds_to_offset(seconds)
|
88
|
+
::DateTime.new(dt.year, dt.mon, dt.mday, dt.hour, dt.min, dt.sec, offset)
|
89
|
+
else
|
90
|
+
value.to_datetime
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def dump(value)
|
95
|
+
if value.respond_to?(:to_datetime)
|
96
|
+
value.to_datetime.iso8601
|
97
|
+
else
|
98
|
+
value.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def string_utc_offset(string)
|
105
|
+
Date._parse(string)[:offset]
|
106
|
+
end
|
107
|
+
|
108
|
+
def seconds_to_offset(seconds)
|
109
|
+
ActiveSupport::TimeZone.seconds_to_utc_offset(seconds)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class IntegerType < Value
|
114
|
+
def cast(value)
|
115
|
+
if value == true
|
116
|
+
1
|
117
|
+
elsif value == false
|
118
|
+
0
|
119
|
+
elsif value.is_a?(String) && value.blank?
|
120
|
+
nil
|
121
|
+
elsif value.is_a?(Float) && !value.finite?
|
122
|
+
nil
|
123
|
+
elsif !value.respond_to?(:to_i)
|
124
|
+
nil
|
125
|
+
else
|
126
|
+
value.to_i
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class BigDecimalType < Value
|
132
|
+
def cast(value)
|
133
|
+
if value == true
|
134
|
+
1
|
135
|
+
elsif value == false
|
136
|
+
0
|
137
|
+
elsif value.is_a?(Symbol)
|
138
|
+
value.to_s.to_d
|
139
|
+
elsif value.is_a?(String) && value.blank?
|
140
|
+
nil
|
141
|
+
elsif value.is_a?(Float) && !value.finite?
|
142
|
+
nil
|
143
|
+
elsif !value.respond_to?(:to_d)
|
144
|
+
nil
|
145
|
+
else
|
146
|
+
value.to_d
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class FloatType < Value
|
152
|
+
def cast(value)
|
153
|
+
if value == true
|
154
|
+
1
|
155
|
+
elsif value == false
|
156
|
+
0
|
157
|
+
elsif value.is_a?(Symbol)
|
158
|
+
value.to_s.to_f
|
159
|
+
elsif value.is_a?(String) && value.blank?
|
160
|
+
nil
|
161
|
+
elsif value.is_a?(Float) && !value.finite?
|
162
|
+
nil
|
163
|
+
elsif !value.respond_to?(:to_f)
|
164
|
+
nil
|
165
|
+
else
|
166
|
+
value.to_f
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class JsonType < Value
|
172
|
+
def cast(value)
|
173
|
+
return value unless value.is_a?(String)
|
174
|
+
|
175
|
+
begin
|
176
|
+
ActiveSupport::JSON.decode(value)
|
177
|
+
rescue StandardError
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def dump(value)
|
183
|
+
ActiveSupport::JSON.encode(value) unless value.nil?
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
module Registry
|
188
|
+
module_function
|
189
|
+
|
190
|
+
@registrations = {}
|
191
|
+
|
192
|
+
def register(name, klass = nil)
|
193
|
+
@registrations[name.to_sym] = klass
|
194
|
+
end
|
195
|
+
|
196
|
+
def lookup(name)
|
197
|
+
name ||= :string
|
198
|
+
@registrations[name.to_sym]
|
199
|
+
end
|
200
|
+
|
201
|
+
register(:string, StringType)
|
202
|
+
register(:date, DateType)
|
203
|
+
register(:datetime, DateTimeType)
|
204
|
+
register(:bigdecimal, BigDecimalType)
|
205
|
+
register(:integer, IntegerType)
|
206
|
+
register(:float, FloatType)
|
207
|
+
register(:json, JsonType)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/lib/aliyun/log/request.rb
CHANGED
@@ -7,6 +7,8 @@ require 'digest'
|
|
7
7
|
require 'date'
|
8
8
|
require 'zlib'
|
9
9
|
|
10
|
+
require_relative 'utils'
|
11
|
+
|
10
12
|
module Aliyun
|
11
13
|
module Log
|
12
14
|
class Request
|
@@ -16,25 +18,6 @@ module Aliyun
|
|
16
18
|
@config = config
|
17
19
|
end
|
18
20
|
|
19
|
-
def get_resource_path(resources = {})
|
20
|
-
resources ||= {}
|
21
|
-
res = '/'
|
22
|
-
if resources[:logstore]
|
23
|
-
res = "#{res}logstores"
|
24
|
-
res = "#{res}/#{resources[:logstore]}" unless resources[:logstore].empty?
|
25
|
-
end
|
26
|
-
res = "#{res}/#{resources[:action]}" if resources[:action]
|
27
|
-
res
|
28
|
-
end
|
29
|
-
|
30
|
-
def get_request_url(resources = {})
|
31
|
-
resources ||= {}
|
32
|
-
url = URI.parse(@config.endpoint)
|
33
|
-
url.host = "#{resources[:project]}." + url.host if resources[:project]
|
34
|
-
url.path = get_resource_path(resources)
|
35
|
-
url.to_s
|
36
|
-
end
|
37
|
-
|
38
21
|
def get(resources, payload = {})
|
39
22
|
do_request('GET', resources, payload)
|
40
23
|
end
|
@@ -52,10 +35,11 @@ module Aliyun
|
|
52
35
|
end
|
53
36
|
|
54
37
|
def do_request(verb, resources, payload)
|
55
|
-
resource_path = get_resource_path(resources)
|
38
|
+
resource_path = Utils.get_resource_path(resources)
|
39
|
+
url = Utils.get_request_url(@config.endpoint, resources)
|
56
40
|
request_options = {
|
57
41
|
method: verb,
|
58
|
-
url:
|
42
|
+
url: url,
|
59
43
|
open_timeout: @config.open_timeout,
|
60
44
|
read_timeout: @config.read_timeout
|
61
45
|
}
|
@@ -75,15 +59,17 @@ module Aliyun
|
|
75
59
|
response = request.execute do |resp|
|
76
60
|
if resp.code >= 300
|
77
61
|
e = ServerError.new(resp)
|
78
|
-
logger.error(e.to_s)
|
62
|
+
logger.error("#{e.to_s} #{resp.code} #{resp}")
|
79
63
|
raise e
|
80
64
|
else
|
81
65
|
resp.return!
|
82
66
|
end
|
83
67
|
end
|
84
68
|
|
85
|
-
logger.debug("Received HTTP response, code: #{response.code},
|
86
|
-
"#{
|
69
|
+
logger.debug("Received HTTP response, code: #{response.code}, " \
|
70
|
+
"url: #{request_options[:url]}, " \
|
71
|
+
"method: #{verb}, headers: #{response.headers}, " \
|
72
|
+
"body: #{response.body.force_encoding('UTF-8')}")
|
87
73
|
|
88
74
|
response
|
89
75
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
|
5
|
+
module Aliyun
|
6
|
+
module Log
|
7
|
+
module Utils
|
8
|
+
module_function
|
9
|
+
def get_resource_path(resources = {})
|
10
|
+
resources ||= {}
|
11
|
+
res = '/'
|
12
|
+
if resources[:logstore]
|
13
|
+
res = "#{res}logstores"
|
14
|
+
res = "#{res}/#{resources[:logstore]}" unless resources[:logstore].empty?
|
15
|
+
end
|
16
|
+
res = "#{res}/#{resources[:action]}" if resources[:action]
|
17
|
+
res
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_request_url(endpoint, resources = {})
|
21
|
+
resources ||= {}
|
22
|
+
url = URI.parse(endpoint)
|
23
|
+
url.host = "#{resources[:project]}." + url.host if resources[:project]
|
24
|
+
url.path = get_resource_path(resources)
|
25
|
+
url.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/aliyun/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aliyun-log
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yingce Liu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -114,6 +114,20 @@ dependencies:
|
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '3.0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: webmock
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '3.0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '3.0'
|
117
131
|
description: Aliyun Log SDK for Ruby 阿里云日志服务(SLS) Ruby SDK, 目前仅实现基于Restfull部分接口和简单Model映射
|
118
132
|
email:
|
119
133
|
- yingce@live.com
|
@@ -137,8 +151,11 @@ files:
|
|
137
151
|
- lib/aliyun/log/record/persistence.rb
|
138
152
|
- lib/aliyun/log/record/relation.rb
|
139
153
|
- lib/aliyun/log/record/scope_registry.rb
|
154
|
+
- lib/aliyun/log/record/scoping.rb
|
155
|
+
- lib/aliyun/log/record/type_casting.rb
|
140
156
|
- lib/aliyun/log/request.rb
|
141
157
|
- lib/aliyun/log/server_error.rb
|
158
|
+
- lib/aliyun/log/utils.rb
|
142
159
|
- lib/aliyun/version.rb
|
143
160
|
homepage: https://github.com/yingce/aliyun-log
|
144
161
|
licenses:
|