aliyun-log 0.2.0 → 0.2.10

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: 4cc759f537d07276e59933f681a0014ae89066e65c98b236c70e541f52173d85
4
- data.tar.gz: a3fcb9eff51e48f11b3b578e47cbfc878d3796f0f9c1b0ad5ffa0638f9024ff6
3
+ metadata.gz: b0d96cc9d1e5e1a2bee7cdcbca04043f3a80e8b733b1be820aac363b31f8ec34
4
+ data.tar.gz: 2769ddbdd8d54f257e02d12813d207fbb920c50659977a42527d6f714b45b818
5
5
  SHA512:
6
- metadata.gz: e7a836e5dc43c44fc22a22304fa1a140d0b230fde3047271fb65a70f8c866dfc0ec014e86b12666f2ce9cb7de73487038cab63698e5a21a6a423d7f69f8a3419
7
- data.tar.gz: 46e1132a1d9a81af0e2279a6f847abe73f2c23265a7aea018f289b63b85a6bdfa6a5415031c1df45087c8aca8f1e61b02e22a6ce76f8e4e0401278673893962e
6
+ metadata.gz: 2a3c0526d83176a4b34e111744ebac7cd0543fc1396462f0299dcda7c43ce31b12fb7b8ebb08898a08edddaa684ea16e3288d1985add5f35eaf61cc8ca4222e8
7
+ data.tar.gz: d658a42dd7aff881cb7cc9b31374af83417533185cca1c3e4b8763ec4c52bd85bd22749f4b5130dede0b42fdca0ec84663db469c5a9893d126cd9b49972fb198
@@ -38,7 +38,7 @@ module Aliyun
38
38
  def self.logger
39
39
  unless @logger
40
40
  @logger = Logger.new(
41
- @log_file ||= Config.log_file, MAX_NUM_LOG, ROTATE_SIZE
41
+ @log_file ||= Config.log_file || IO::NULL, MAX_NUM_LOG, ROTATE_SIZE
42
42
  )
43
43
  @logger.level = Logging.logger_level
44
44
  end
@@ -7,8 +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_file = 'aliyun_log.log'
11
- @log_level = Logger::DEBUG
10
+ @log_level = Logger::WARN
11
+ @log_file = STDOUT
12
12
  @timestamps = true
13
13
  class << self
14
14
  attr_accessor :endpoint, :access_key_id, :access_key_secret,
@@ -30,13 +30,7 @@ module Aliyun
30
30
  end
31
31
 
32
32
  def put_log(attributes)
33
- contents = attributes.map { |k, v| { key: k.to_s, value: v.to_s } }
34
- log = Aliyun::Log::Protobuf::Log.new(
35
- time: Time.now.to_i,
36
- contents: contents
37
- )
38
- log_group = Aliyun::Log::Protobuf::LogGroup.new(logs: [log])
39
- put_logs(log_group)
33
+ @protocol.put_log(project_name, name, attributes)
40
34
  end
41
35
 
42
36
  def get_logs(opts = {})
@@ -0,0 +1,13 @@
1
+ module Aliyun
2
+ module Log
3
+ class LogTail < Common::AttrStruct
4
+ attrs :name, :log_type, :log_path, :file_pattern, :localstore, :time_format,
5
+ :log_begin_regex, :regex, :key, :topic_format,
6
+ :filterKey, :filter_regex, :file_encoding
7
+ def initialize(opts, protocol)
8
+ super(opts)
9
+ @protocol = protocol
10
+ end
11
+ end
12
+ end
13
+ end
@@ -108,7 +108,7 @@ module Aliyun
108
108
  def build_log_pb(attrs, time = Time.now.to_i)
109
109
  logs = attrs.is_a?(Array) ? attrs : [attrs]
110
110
  logs.map do |log_attr|
111
- contents = log_attr.compact.map { |k, v| { key: k, value: v.to_s } }
111
+ contents = log_attr.map { |k, v| { key: k, value: v.to_s } }
112
112
  Protobuf::Log.new(time: time, contents: contents)
113
113
  end
114
114
  end
@@ -182,7 +182,7 @@ module Aliyun
182
182
  keys: {}
183
183
  }
184
184
  fields.each do |k, v|
185
- v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].blank?
185
+ v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].nil?
186
186
  body[:keys][k] = v
187
187
  end
188
188
  @http.post({ project: project_name, logstore: logstore_name, action: 'index' }, body.to_json)
@@ -196,7 +196,7 @@ module Aliyun
196
196
  keys: {}
197
197
  }
198
198
  fields.each do |k, v|
199
- v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].blank?
199
+ v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].nil?
200
200
  body[:keys][k] = v
201
201
  end
202
202
  @http.put({ project: project_name, logstore: logstore_name, action: 'index' }, body.to_json)
@@ -9,7 +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'
12
+ require_relative 'record/scoping'
13
13
 
14
14
  module Aliyun
15
15
  module Log
@@ -27,45 +27,30 @@ module Aliyun
27
27
 
28
28
  Log.included_models << self unless Log.included_models.include? self
29
29
 
30
- field :created_at, :text if Config.timestamps
30
+ field :created_at, type: :text, cast_type: :datetime if Config.timestamps
31
31
 
32
32
  define_model_callbacks :save, :create, :initialize
33
33
 
34
- after_initialize :set_created_at
34
+ before_save :set_created_at
35
35
  end
36
36
 
37
37
  include Field
38
38
  include Persistence
39
+ include Scoping
39
40
 
40
41
  include ActiveModel::AttributeMethods
41
42
 
42
43
  module ClassMethods
43
44
  def logstore(options = {})
44
- if options[:timestamps] && !Config.timestamps
45
+ opt = options.dup
46
+ if opt[:timestamps] && !Config.timestamps
45
47
  field :created_at, :text
46
- elsif options[:timestamps] == false && Config.timestamps
48
+ elsif opt[:timestamps] == false && Config.timestamps
47
49
  remove_field :created_at
48
50
  end
49
- self._schema_load = true if options[:auto_sync] == false
50
- self.options = options
51
- end
52
-
53
- Relation.instance_methods(false).each do |method|
54
- define_method(method) do |args|
55
- relation.public_send(method, args)
56
- end
57
- end
58
-
59
- %i[count load result].each do |method|
60
- define_method(method) do
61
- relation.public_send(method)
62
- end
63
- end
64
-
65
- private
66
-
67
- def relation
68
- Relation.new(self)
51
+ self._schema_load = true if opt[:auto_sync] == false
52
+ opt[:field_doc_value] = opt[:field_doc_value] != false
53
+ self.options = opt
69
54
  end
70
55
  end
71
56
 
@@ -101,7 +86,7 @@ module Aliyun
101
86
  end.compact.join(', ')
102
87
  else
103
88
  'not initialized'
104
- end
89
+ end
105
90
 
106
91
  "#<#{self.class} #{inspection}>"
107
92
  end
@@ -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,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require "active_support"
3
- require "active_support/time"
4
- require "active_support/core_ext"
5
- require "active_model"
2
+
3
+ require_relative 'type_casting'
6
4
 
7
5
  module Aliyun
8
6
  module Log
@@ -22,13 +20,16 @@ module Aliyun
22
20
 
23
21
  included do
24
22
  class_attribute :attributes, instance_accessor: false, default: {}
23
+ extend Common::Logging
25
24
  end
26
25
 
27
26
  module ClassMethods
28
- def field(name, type = :text, options = {})
27
+ def field(name, options = {})
28
+ type = options[:type] || :text
29
29
  unless PERMITTED_KEY_TYPES.include?(type)
30
30
  raise ArgumentError, "Field #{name} type(#{type}) error, key type only support text/long/double/json"
31
31
  end
32
+
32
33
  named = name.to_s
33
34
  self.attributes = attributes.merge(name => { type: type }.merge(options))
34
35
 
@@ -36,7 +37,7 @@ module Aliyun
36
37
  warn_about_method_overriding("#{named}=", name)
37
38
  warn_about_method_overriding("#{named}?", name)
38
39
 
39
- define_attribute_method(name) # Dirty API
40
+ define_attribute_method(name)
40
41
 
41
42
  generated_methods.module_eval do
42
43
  define_method(named) { read_attribute(named) }
@@ -64,7 +65,6 @@ module Aliyun
64
65
  remove_method field
65
66
  remove_method :"#{field}="
66
67
  remove_method :"#{field}?"
67
- remove_method :"#{field}_before_type_cast"
68
68
  end
69
69
  end
70
70
 
@@ -80,7 +80,8 @@ module Aliyun
80
80
 
81
81
  def warn_about_method_overriding(method_name, field_name)
82
82
  if instance_methods.include?(method_name.to_sym)
83
- 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')
84
85
  end
85
86
  end
86
87
  end
@@ -106,7 +107,7 @@ module Aliyun
106
107
  attr_accessor :attributes
107
108
 
108
109
  def write_attribute(name, value)
109
- attributes[name.to_sym] = value
110
+ attributes[name.to_sym] = TypeCasting.cast_field(value, self.class.attributes[name.to_sym])
110
111
  end
111
112
 
112
113
  alias []= write_attribute
@@ -117,7 +118,7 @@ module Aliyun
117
118
  alias [] read_attribute
118
119
 
119
120
  def set_created_at
120
- 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?
121
122
  end
122
123
 
123
124
  def timestamps_enabled?
@@ -8,7 +8,8 @@ module Aliyun
8
8
 
9
9
  module ClassMethods
10
10
  def logstore_name
11
- @logstore_name ||= options[:name] || base_class.name.split('::').last.downcase.pluralize
11
+ @logstore_name ||= options[:name] ||
12
+ base_class.name.split('::').last.underscore.pluralize
12
13
  end
13
14
 
14
15
  def logstore_name=(value)
@@ -55,9 +56,8 @@ module Aliyun
55
56
  def create(data, opts = {})
56
57
  auto_load_schema
57
58
  if data.is_a?(Array)
58
- logs = []
59
- data.each do |log_attr|
60
- logs << new(log_attr).save_array
59
+ logs = data.map do |log_attr|
60
+ new(log_attr).save_array
61
61
  end
62
62
  res = Log.record_connection.put_log(project_name, logstore_name, logs, opts)
63
63
  res.code == 200
@@ -79,10 +79,10 @@ module Aliyun
79
79
  end
80
80
 
81
81
  def field_indices
82
- if options[:field_index] == true
83
- attributes.reject { |_, value| value[:index] == false }
84
- else
82
+ if options[:field_index] == false
85
83
  attributes.select { |_, value| value[:index] == true }
84
+ else
85
+ attributes.reject { |_, value| value[:index] == false }
86
86
  end
87
87
  end
88
88
 
@@ -97,18 +97,38 @@ module Aliyun
97
97
  def update_index
98
98
  conf_res = Log.record_connection.get_index(project_name, logstore_name)
99
99
  raw_conf = JSON.parse(conf_res)
100
- index_conf = raw_conf.dup
101
- field_indices.each do |k, v|
102
- index_conf['keys'][k.to_s] ||= v
100
+ index_conf = raw_conf.deep_dup
101
+ field_index_types.each do |k, v|
102
+ index_conf['keys'][k.to_s] ||= {}
103
+ index_conf['keys'][k.to_s].merge!(v.as_json)
103
104
  end
104
105
  return if index_conf['keys'] == raw_conf['keys']
105
106
 
106
107
  Log.record_connection.update_index(
107
108
  project_name,
108
109
  logstore_name,
109
- index_conf['keys']
110
+ index_conf['keys'].with_indifferent_access
110
111
  )
111
112
  end
113
+
114
+ def field_index_types
115
+ field_indices.tap do |tap|
116
+ tap.each do |_, v|
117
+ v[:alias] ||= ''
118
+ v[:caseSensitive] ||= false
119
+ v[:chn] ||= false
120
+ v[:doc_value] = options[:field_doc_value] != false if v[:doc_value].nil?
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ def dump_attributes
127
+ attributes.dup.tap do |tap|
128
+ tap.each do |k, v|
129
+ tap[k] = TypeCasting.dump_field(v, self.class.attributes[k])
130
+ end
131
+ end
112
132
  end
113
133
 
114
134
  def save
@@ -116,7 +136,11 @@ module Aliyun
116
136
  run_callbacks(:create) do
117
137
  run_callbacks(:save) do
118
138
  if valid?
119
- res = Log.record_connection.put_log(self.class.project_name, self.class.logstore_name, attributes)
139
+ res = Log.record_connection.put_log(
140
+ self.class.project_name,
141
+ self.class.logstore_name,
142
+ dump_attributes
143
+ )
120
144
  res.code == 200
121
145
  else
122
146
  false
@@ -128,7 +152,7 @@ module Aliyun
128
152
  def save_array
129
153
  run_callbacks(:create) do
130
154
  run_callbacks(:save) do
131
- validate! && attributes
155
+ validate! && dump_attributes
132
156
  end
133
157
  end
134
158
  end
@@ -8,7 +8,49 @@ module Aliyun
8
8
  @klass = klass
9
9
  @opts = opts
10
10
  @klass.auto_load_schema
11
- @opts[:search] ||= '*'
11
+ end
12
+
13
+ def inspect
14
+ "#<#{self.class}>"
15
+ end
16
+
17
+ def first(line = 1)
18
+ find_offset(0, line, false)
19
+ end
20
+
21
+ def second
22
+ find_offset(1)
23
+ end
24
+
25
+ def third
26
+ find_offset(2)
27
+ end
28
+
29
+ def fourth
30
+ find_offset(3)
31
+ end
32
+
33
+ def fifth
34
+ find_offset(4)
35
+ end
36
+
37
+ def last(line = 1)
38
+ find_offset(0, line, true)
39
+ end
40
+
41
+ def find_offset(nth, line = 1, reverse = false)
42
+ @opts[:line] = line
43
+ @opts[:offset] = nth
44
+ @opts[:reverse] = reverse
45
+ line <= 1 ? load[0] : load
46
+ end
47
+
48
+ def scoping
49
+ previous = @klass.current_scope
50
+ @klass.current_scope = self
51
+ yield
52
+ ensure
53
+ @klass.current_scope = previous
12
54
  end
13
55
 
14
56
  def from(from)
@@ -44,13 +86,18 @@ module Aliyun
44
86
  self
45
87
  end
46
88
 
47
- def search(str)
48
- @opts[:search] = str
89
+ def search(*statement)
90
+ ql = statement_ql(*statement)
91
+ @opts[:search] = ql if ql.present?
49
92
  self
50
93
  end
51
94
 
52
- def sql(str)
53
- @opts[:sql] = str
95
+ def sql(*statement)
96
+ unless statement[0].is_a?(String)
97
+ raise ParseStatementInvalid, 'Only support string statement'
98
+ end
99
+ ql = sanitize_array(*statement)
100
+ @opts[:sql] = ql if ql.present?
54
101
  self
55
102
  end
56
103
 
@@ -77,7 +124,7 @@ module Aliyun
77
124
  query[:line] ||= 100
78
125
  query[:offset] = query[:page] * query[:line]
79
126
  end
80
- query[:query] = query[:search]
127
+ query[:query] = query[:search] || '*'
81
128
  query[:query] = "#{query[:query]}|#{query[:sql]}" if query[:sql].present?
82
129
  res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
83
130
  JSON.parse(res)
@@ -85,11 +132,131 @@ module Aliyun
85
132
 
86
133
  def load
87
134
  result.map do |json_attr|
88
- attrs = {}
89
- @klass.attributes.keys.each do |k, _|
90
- attrs[k] = json_attr[k.to_s]
135
+ record = @klass.new
136
+ json_attr.each do |key, value|
137
+ record.send("#{key}=", value) if record.respond_to?("#{key}=")
138
+ end
139
+ record
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def statement_ql(*statement)
146
+ if statement.size == 1
147
+ sanitize_hash(statement.first)
148
+ elsif statement.size > 1
149
+ sanitize_array(*statement)
150
+ end
151
+ end
152
+
153
+ def sanitize_hash(search_content)
154
+ return search_content unless search_content.is_a?(Hash)
155
+
156
+ search_content.select { |_, v| v.present? }.map do |key, value|
157
+ options = @klass.attributes[:"#{key}"]
158
+ unless options
159
+ raise UnknownAttributeError, "unknown field '#{key}' for #{@klass.name}."
160
+ end
161
+
162
+ raise_if_hash_quote(value)
163
+
164
+ cast_type = options[:cast_type]
165
+ if value.is_a?(Array)
166
+ values = value.uniq.map { |v| _quote(cast_type, v) }
167
+ str_values = values.map { |v| "#{key}: #{v}" }.join(' OR ')
168
+ values.size > 1 ? "(#{str_values})" : str_values
169
+ elsif value.is_a?(Range)
170
+ "#{key} in [#{value.begin} #{value.end}]"
171
+ else
172
+ "#{key}: #{_quote(cast_type, value)}"
173
+ end
174
+ end.join(' AND ')
175
+ end
176
+
177
+ def sanitize_array(*ary)
178
+ statement, *values = ary
179
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
180
+ replace_named_bind_variables(statement, values.first)
181
+ elsif statement.include?('?')
182
+ replace_bind_variables(statement, values)
183
+ elsif statement.blank? || values.blank?
184
+ statement
185
+ else
186
+ statement % values.collect(&:to_s)
187
+ end
188
+ end
189
+
190
+ def replace_named_bind_variables(statement, bind_vars)
191
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
192
+ if bind_vars.include?(match = Regexp.last_match(2).to_sym)
193
+ match_value = bind_vars[match]
194
+ raise_if_hash_quote(match_value)
195
+ if match_value.is_a?(Array) || match_value.is_a?(Range)
196
+ values = match_value.map { |v| _quote_type_value(v) }
197
+ values.join(', ')
198
+ else
199
+ _quote_type_value(match_value)
200
+ end
201
+ else
202
+ raise ParseStatementInvalid, "missing value for :#{match} in #{statement}"
91
203
  end
92
- @klass.new(attrs)
204
+ end
205
+ end
206
+
207
+ def replace_bind_variables(statement, values)
208
+ expected = statement.count('?')
209
+ provided = values.size
210
+ if expected != provided
211
+ raise ParseStatementInvalid, "wrong number of bind variables (#{provided} " \
212
+ "for #{expected}) in: #{statement}"
213
+ end
214
+ bound = values.dup
215
+ statement.gsub(/\?/) do
216
+ value = bound.shift
217
+ raise_if_hash_quote(value)
218
+ if value.is_a?(Array) || value.is_a?(Range)
219
+ values = value.map { |v| _quote_type_value(v) }
220
+ values.join(', ')
221
+ else
222
+ _quote_type_value(value)
223
+ end
224
+ end
225
+ end
226
+
227
+ def _quote(type, value)
228
+ v = TypeCasting.dump_field(value, cast_type: type || :string)
229
+ case type
230
+ when :string, nil then "'#{v.to_s}'"
231
+ when :bigdecimal then v.to_s("F")
232
+ when :integer then v.to_s.to_i
233
+ when :datetime, :date then "'#{v.iso8601}'"
234
+ else
235
+ value.to_s
236
+ end
237
+ end
238
+
239
+ def _quote_type_value(value)
240
+ case value.class.name
241
+ when 'String' then "'#{value.to_s}'"
242
+ when 'BigDecimal' then value.to_s("F")
243
+ when 'Date', 'DateTime', 'Time' then "'#{value.iso8601}'"
244
+ else
245
+ value
246
+ end
247
+ end
248
+
249
+ def raise_if_hash_quote(value)
250
+ if value.is_a?(Hash) || value.is_a?(ActiveSupport::HashWithIndifferentAccess)
251
+ raise ParseStatementInvalid, "can't quote Hash"
252
+ end
253
+ end
254
+
255
+ def method_missing(method, *args, &block)
256
+ if @klass.respond_to?(method)
257
+ scoping { @klass.public_send(method, *args, &block) }
258
+ else
259
+ super
93
260
  end
94
261
  end
95
262
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aliyun
4
+ module Log
5
+ module PerThreadRegistry
6
+ def self.extended(object)
7
+ object.instance_variable_set '@per_thread_registry_key', object.name.freeze
8
+ end
9
+
10
+ def instance
11
+ Thread.current[@per_thread_registry_key] ||= new
12
+ end
13
+
14
+ private
15
+
16
+ def method_missing(name, *args, &block)
17
+ singleton_class.delegate name, to: :instance
18
+
19
+ send(name, *args, &block)
20
+ end
21
+ end
22
+
23
+ class ScopeRegistry
24
+ extend PerThreadRegistry
25
+
26
+ def initialize
27
+ @registry = Hash.new { |hash, key| hash[key] = {} }
28
+ end
29
+
30
+ def value_for(scope_type, model)
31
+ @registry[scope_type][model.name]
32
+ end
33
+
34
+ # Sets the +value+ for a given +scope_type+ and +model+.
35
+ def set_value_for(scope_type, model, value)
36
+ @registry[scope_type][model.name] = value
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,55 @@
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, to: :all
14
+ delegate :where, :query, :search, :sql, :from, :to,
15
+ :page, :line, :limit, :offset, to: :all
16
+ delegate :first, :last, :second, :third, :fourth, :fifth, :find_offset, to: :all
17
+
18
+ def current_scope
19
+ ScopeRegistry.value_for(:current_scope, self)
20
+ end
21
+
22
+ def current_scope=(scope)
23
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
24
+ end
25
+
26
+ def scope(name, body)
27
+ raise ArgumentError, 'The scope body needs to be callable.' unless body.respond_to?(:call)
28
+
29
+ singleton_class.send(:define_method, name) do |*args|
30
+ scope = all
31
+ scope = scope.scoping { body.call(*args) }
32
+ scope
33
+ end
34
+ end
35
+
36
+ def unscoped
37
+ block_given? ? relation.scoping { yield } : relation
38
+ end
39
+
40
+ def all
41
+ scope = current_scope
42
+ scope ||= relation.from(0).to(Time.now.to_i)
43
+ scope
44
+ end
45
+
46
+ private
47
+
48
+ def relation
49
+ Relation.new(self)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,191 @@
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: :bigdecimal,
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 JsonType < Value
152
+ def cast(value)
153
+ return value unless value.is_a?(String)
154
+
155
+ begin
156
+ ActiveSupport::JSON.decode(value)
157
+ rescue StandardError
158
+ nil
159
+ end
160
+ end
161
+
162
+ def dump(value)
163
+ ActiveSupport::JSON.encode(value) unless value.nil?
164
+ end
165
+ end
166
+
167
+ module Registry
168
+ module_function
169
+
170
+ @registrations = {}
171
+
172
+ def register(name, klass = nil)
173
+ @registrations[name.to_sym] = klass
174
+ end
175
+
176
+ def lookup(name)
177
+ name ||= :string
178
+ @registrations[name.to_sym]
179
+ end
180
+
181
+ register(:string, StringType)
182
+ register(:date, DateType)
183
+ register(:datetime, DateTimeType)
184
+ register(:bigdecimal, BigDecimalType)
185
+ register(:integer, IntegerType)
186
+ register(:json, JsonType)
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -83,7 +83,7 @@ module Aliyun
83
83
  end
84
84
 
85
85
  logger.debug("Received HTTP response, code: #{response.code}, headers: " \
86
- "#{response.headers}, body: #{response.body}")
86
+ "#{response.headers}, body: #{response.body.force_encoding('UTF-8')}")
87
87
 
88
88
  response
89
89
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aliyun
4
4
  module Log
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.10'
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.0
4
+ version: 0.2.10
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-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -114,7 +114,7 @@ dependencies:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
116
  version: '3.0'
117
- description: Aliyun Log SDK for Ruby 阿里云日志服务(SLS) Ruby SDK, 目前仅实现基于Restfull部分接口
117
+ description: Aliyun Log SDK for Ruby 阿里云日志服务(SLS) Ruby SDK, 目前仅实现基于Restfull部分接口和简单Model映射
118
118
  email:
119
119
  - yingce@live.com
120
120
  executables: []
@@ -128,6 +128,7 @@ files:
128
128
  - lib/aliyun/log/common/logging.rb
129
129
  - lib/aliyun/log/config.rb
130
130
  - lib/aliyun/log/logstore.rb
131
+ - lib/aliyun/log/logtail.rb
131
132
  - lib/aliyun/log/project.rb
132
133
  - lib/aliyun/log/protobuf.rb
133
134
  - lib/aliyun/log/protocol.rb
@@ -136,6 +137,9 @@ files:
136
137
  - lib/aliyun/log/record/field.rb
137
138
  - lib/aliyun/log/record/persistence.rb
138
139
  - lib/aliyun/log/record/relation.rb
140
+ - lib/aliyun/log/record/scope_registry.rb
141
+ - lib/aliyun/log/record/scoping.rb
142
+ - lib/aliyun/log/record/type_casting.rb
139
143
  - lib/aliyun/log/request.rb
140
144
  - lib/aliyun/log/server_error.rb
141
145
  - lib/aliyun/version.rb