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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed78b39497d6a671405b2d815292ca1e1e11ee301d816395264add76a9bc1235
4
- data.tar.gz: 1b2f92c27745b27be61ca5f8fbb41470eaffcd710b22979671604173646c5450
3
+ metadata.gz: ba1bcbd83277ca8da27978ecd7a603a205a19c1600b11f359135e2629e6f61c4
4
+ data.tar.gz: d1e731f6b181167ef11004c7c79bbbef9acd290be531a08a24bb6f87134cfaac
5
5
  SHA512:
6
- metadata.gz: 19e6a18d2c168882054e14875da1baa18bab32217b2626a33e209e25c867b011deb5cba7d0b6de2f4af40a67ff4c66fb12ff3908881d9e28ced5ca8020c152b4
7
- data.tar.gz: 7cfdddb1c6d804b4f62ee34cac200d4eb869ff32c70cf5bda5a4fd10947d06c5df61ba273662aff908a57e685a8ce2c586d1e3efed8d71e1dc2c8a59b649e117
6
+ metadata.gz: 850989116e4820287e15fbe5361319f1043d4a1c3e7991bab947187bcac2d0114ee9244df26c4c132cd29d400752b4f18a6c0a3bcccaaabdda151858829b19e4
7
+ data.tar.gz: c03afaddd81499c4cb36be357d9894eccf2574fa009afa2d57f7d01e61885534a60b94f4bc2dfaa315e5db0862481225966fc71f27fb8ce1d6a1686284f90e40
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'version'
3
4
  require_relative 'log/common'
4
5
  require_relative 'log/client'
5
6
  require_relative 'log/config'
@@ -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::DEBUG
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,
@@ -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/relation'
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 = {})
@@ -4,6 +4,7 @@ module Aliyun
4
4
  class ArgumentError < StandardError; end
5
5
  class UnknownAttributeError < StandardError; end
6
6
  class ProjectNameError < StandardError; end
7
+ class ParseStatementInvalid < StandardError; end
7
8
  end
8
9
  end
9
10
  end
@@ -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, type = :text, options = {})
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) # Dirty API
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
- Common::Logging.logger.warn("Method #{method_name} generated for the field #{field_name} overrides already existing method")
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).to_s if timestamps_enabled?
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
- data.each do |log_attr|
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
- indices = if options[:field_index] == false
84
- attributes.select { |_, value| value[:index] == true }
85
- else
86
- attributes.reject { |_, value| value[:index] == false }
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.dup
108
- field_indices.each do |k, v|
109
- index_conf['keys'][k.to_s] ||= v
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(self.class.project_name, self.class.logstore_name, attributes)
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! && attributes
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[:reverse] = reverse
46
- line <= 1 ? load[0] : load
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 where(opts)
104
+ def api(opts)
86
105
  @opts.merge!(opts)
87
106
  self
88
107
  end
89
108
 
90
- def search(str)
91
- @opts[:search] = str
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(str)
96
- @opts[:sql] = str
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[:query].blank?
108
- where_cond = query[:sql].split(/where /i)[1] if query[:sql].present?
109
- query[:query] = "#{query[:search]}|SELECT COUNT(*) as count"
110
- query[:query] = "#{query[:query]} WHERE #{where_cond}" if where_cond.present?
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
- res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
113
- res = JSON.parse(res.body)
114
- res[0]['count'].to_i
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
- if query[:page]
120
- query[:line] ||= 100
121
- query[:offset] = query[:page] * query[:line]
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[:query] = query[:search]
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
- attrs = {}
132
- @klass.attributes.keys.each do |k, _|
133
- attrs[k] = json_attr[k.to_s]
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
- @klass.new(attrs)
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
@@ -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: get_request_url(resources),
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}, headers: " \
86
- "#{response.headers}, body: #{response.body.force_encoding('UTF-8')}")
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aliyun
4
4
  module Log
5
- VERSION = '0.2.6'
5
+ VERSION = '0.2.12'
6
6
  end
7
7
  end
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.6
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-06-09 00:00:00.000000000 Z
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: