aliyun-log 0.1.3 → 0.2.9

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: 3d6196edae2aaeb7e8075c3b28b84a7381973910ae2f519ba6147f17980119b1
4
- data.tar.gz: dbba3f0046ffac4762e13cebac50a7a883e6a5a74585c54c8bdda6c43dc7d95a
3
+ metadata.gz: 0365cd8d407b5198a7e6ce3ea9e0aeac77f4af1954a7373a5817e3454a0d72ad
4
+ data.tar.gz: '0842353fac8ae79d4972e351437b8435137169eb41929085796bc70c30973d88'
5
5
  SHA512:
6
- metadata.gz: f22410d5c16d83e0b54f9435301a17b4ec75ff72fbca11c583a95c0f40a8fa734311da4974eb7b0f22fad03b5b0f129bccf9b3f0ae6cdf91800500002bd1a476
7
- data.tar.gz: ddc6c2fa7ad08f0cadeade94268997eae1241ade19b046a9439a4a52cf4e4735bbbc4936835139a5b479ef4d2b07b8e220e4b97b20294f8f5260cee86d54f972
6
+ metadata.gz: 91b72a67855c9850b9b21eb1930f4d1c1118cb8068e3172e9ffd6db15e214003592671f1b5705b2b60f32abe458b4843683946bc837de8118d075855a7ba2cbd
7
+ data.tar.gz: 5926791f61bb63e367337c4bb7b8c284d3aa04d4c94f4ba9966aa54317db53ce75e5dbc4953d804088ec0cd96bfca25af88d18f7e828b3712b06316d457e9cd5
@@ -9,11 +9,33 @@ require_relative 'log/protobuf'
9
9
  require_relative 'log/protocol'
10
10
  require_relative 'log/request'
11
11
  require_relative 'log/server_error'
12
+ require_relative 'log/record'
12
13
 
13
14
  module Aliyun
14
15
  module Log
15
- def self.configure
16
+ extend self
17
+
18
+ def configure
16
19
  block_given? ? yield(Config) : Config
17
20
  end
21
+ alias config configure
22
+
23
+ def included_models
24
+ @included_models ||= []
25
+ end
26
+
27
+ def record_connection
28
+ unless @record_connection
29
+ @record_connection = Protocol.new(Config.new)
30
+ end
31
+ @record_connection
32
+ end
33
+
34
+ def init_logstore
35
+ @included_models.each do |model|
36
+ model.create_logstore
37
+ modle.sync_index
38
+ end
39
+ end
18
40
  end
19
41
  end
@@ -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,11 +7,13 @@ 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
+ @timestamps = true
12
13
  class << self
13
14
  attr_accessor :endpoint, :access_key_id, :access_key_secret,
14
- :open_timeout, :read_timeout, :log_file, :log_level
15
+ :open_timeout, :read_timeout, :log_file, :log_level,
16
+ :timestamps, :project
15
17
 
16
18
  def configure
17
19
  yield self
@@ -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 = {})
@@ -66,7 +66,7 @@ module Aliyun
66
66
 
67
67
  def create_logstore(project_name, logstore_name, opt = {})
68
68
  body = {
69
- logstore_name: logstore_name,
69
+ logstoreName: logstore_name,
70
70
  ttl: opt[:ttl] || 365,
71
71
  shardCount: opt[:shard_count] || 2,
72
72
  autoSplit: opt[:auto_split].nil? ? false : opt[:auto_split],
@@ -105,10 +105,17 @@ module Aliyun
105
105
  @http.post({ project: project_name, logstore: logstore_name, is_pb: true }, content)
106
106
  end
107
107
 
108
- def put_log(project_name, logstore_name, log_attr)
109
- contents = log_attr.compact.map { |k, v| { key: k, value: v } }
110
- log_pb = Protobuf::Log.new(time: Time.now.to_i, contents: contents)
111
- lg_pb = Protobuf::LogGroup.new(logs: [log_pb])
108
+ def build_log_pb(attrs, time = Time.now.to_i)
109
+ logs = attrs.is_a?(Array) ? attrs : [attrs]
110
+ logs.map do |log_attr|
111
+ contents = log_attr.map { |k, v| { key: k, value: v.to_s } }
112
+ Protobuf::Log.new(time: time, contents: contents)
113
+ end
114
+ end
115
+
116
+ def put_log(project_name, logstore_name, logs, opts = {})
117
+ logs = build_log_pb(logs, opts[:time] || Time.now.to_i)
118
+ lg_pb = Protobuf::LogGroup.new(logs: logs, topic: opts[:topic])
112
119
  @http.post({ project: project_name, logstore: logstore_name, is_pb: true }, lg_pb)
113
120
  end
114
121
 
@@ -175,8 +182,8 @@ module Aliyun
175
182
  keys: {}
176
183
  }
177
184
  fields.each do |k, v|
185
+ v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].nil?
178
186
  body[:keys][k] = v
179
- v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type]) && v[:token].blank?
180
187
  end
181
188
  @http.post({ project: project_name, logstore: logstore_name, action: 'index' }, body.to_json)
182
189
  end
@@ -189,8 +196,8 @@ module Aliyun
189
196
  keys: {}
190
197
  }
191
198
  fields.each do |k, v|
199
+ v[:token] = INDEX_DEFAULT_TOKEN if %w[text json].include?(v[:type].to_s) && v[:token].nil?
192
200
  body[:keys][k] = v
193
- v[:token] = INDEX_DEFAULT_TOKEN if v[:type] == 'text' && v[:token].blank?
194
201
  end
195
202
  @http.put({ project: project_name, logstore: logstore_name, action: 'index' }, body.to_json)
196
203
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/time'
5
+ require 'active_support/core_ext'
6
+ require 'active_model'
7
+ require 'forwardable'
8
+
9
+ require_relative 'record/exception'
10
+ require_relative 'record/field'
11
+ require_relative 'record/persistence'
12
+ require_relative 'record/scoping'
13
+
14
+ module Aliyun
15
+ module Log
16
+ module Record
17
+ extend ActiveSupport::Concern
18
+
19
+ included do
20
+ extend ActiveModel::Callbacks
21
+ include ActiveModel::Validations
22
+
23
+ class_attribute :options, instance_accessor: false, default: {}
24
+ class_attribute :base_class, instance_accessor: false, default: self
25
+ class_attribute :log_connection, instance_accessor: false
26
+ class_attribute :_schema_load, default: false
27
+
28
+ Log.included_models << self unless Log.included_models.include? self
29
+
30
+ field :created_at, type: :text, cast_type: :datetime if Config.timestamps
31
+
32
+ define_model_callbacks :save, :create, :initialize
33
+
34
+ before_save :set_created_at
35
+ end
36
+
37
+ include Field
38
+ include Persistence
39
+ include Scoping
40
+
41
+ include ActiveModel::AttributeMethods
42
+
43
+ module ClassMethods
44
+ def logstore(options = {})
45
+ opt = options.dup
46
+ if opt[:timestamps] && !Config.timestamps
47
+ field :created_at, :text
48
+ elsif opt[:timestamps] == false && Config.timestamps
49
+ remove_field :created_at
50
+ end
51
+ self._schema_load = true if opt[:auto_sync] == false
52
+ opt[:field_doc_value] = opt[:field_doc_value] != false
53
+ self.options = opt
54
+ end
55
+ end
56
+
57
+ def initialize(attrs = {})
58
+ run_callbacks :initialize do
59
+ @new_record = true
60
+ @attributes ||= {}
61
+
62
+ attrs_with_defaults = self.class.attributes.each_with_object({}) do |(attribute, options), res|
63
+ res[attribute] = if attrs.key?(attribute)
64
+ attrs[attribute]
65
+ elsif options.key?(:default)
66
+ evaluate_default_value(options[:default])
67
+ end
68
+ end
69
+
70
+ attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
71
+
72
+ attrs_with_defaults.merge(attrs_virtual).each do |key, value|
73
+ if respond_to?("#{key}=")
74
+ send("#{key}=", value)
75
+ else
76
+ raise UnknownAttributeError, "unknown attribute '#{key}' for #{@record.class}."
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def inspect
83
+ inspection = if defined?(@attributes) && @attributes
84
+ self.class.attributes.keys.collect do |name|
85
+ "#{name}: #{attribute_for_inspect(name)}" if has_attribute?(name)
86
+ end.compact.join(', ')
87
+ else
88
+ 'not initialized'
89
+ end
90
+
91
+ "#<#{self.class} #{inspection}>"
92
+ end
93
+
94
+ def attribute_for_inspect(attr_name)
95
+ value = read_attribute(attr_name)
96
+
97
+ if value.is_a?(String) && value.length > 50
98
+ "#{value[0, 50]}...".inspect
99
+ elsif value.is_a?(Date) || value.is_a?(Time)
100
+ %("#{value.to_s(:db)}")
101
+ else
102
+ value.inspect
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,10 @@
1
+ module Aliyun
2
+ module Log
3
+ module Record
4
+ class ArgumentError < StandardError; end
5
+ class UnknownAttributeError < StandardError; end
6
+ class ProjectNameError < StandardError; end
7
+ class ParseStatementInvalid < StandardError; end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'type_casting'
4
+
5
+ module Aliyun
6
+ module Log
7
+ module Record
8
+ module Field
9
+ extend ActiveSupport::Concern
10
+
11
+ # Types allowed in indexes:
12
+ PERMITTED_KEY_TYPES = %i[
13
+ text
14
+ long
15
+ double
16
+ json
17
+ ].freeze
18
+
19
+ DEFAULT_INDEX_TOKEN = ", '\";=()[]{}?@&<>/:\n\t\r".split('')
20
+
21
+ included do
22
+ class_attribute :attributes, instance_accessor: false, default: {}
23
+ extend Common::Logging
24
+ end
25
+
26
+ module ClassMethods
27
+ def field(name, options = {})
28
+ type = options[:type] || :text
29
+ unless PERMITTED_KEY_TYPES.include?(type)
30
+ raise ArgumentError, "Field #{name} type(#{type}) error, key type only support text/long/double/json"
31
+ end
32
+
33
+ named = name.to_s
34
+ self.attributes = attributes.merge(name => { type: type }.merge(options))
35
+
36
+ warn_about_method_overriding(name, name)
37
+ warn_about_method_overriding("#{named}=", name)
38
+ warn_about_method_overriding("#{named}?", name)
39
+
40
+ define_attribute_method(name)
41
+
42
+ generated_methods.module_eval do
43
+ define_method(named) { read_attribute(named) }
44
+ define_method("#{named}?") do
45
+ value = read_attribute(named)
46
+ case value
47
+ when true then true
48
+ when false, nil then false
49
+ else
50
+ !value.nil?
51
+ end
52
+ end
53
+ define_method("#{named}=") { |value| write_attribute(named, value) }
54
+ end
55
+ end
56
+
57
+ def remove_field(field)
58
+ field = field.to_sym
59
+ attributes.delete(field) || raise('No such field')
60
+
61
+ undefine_attribute_methods
62
+ define_attribute_methods attributes.keys
63
+
64
+ generated_methods.module_eval do
65
+ remove_method field
66
+ remove_method :"#{field}="
67
+ remove_method :"#{field}?"
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def generated_methods
74
+ @generated_methods ||= begin
75
+ Module.new.tap do |mod|
76
+ include(mod)
77
+ end
78
+ end
79
+ end
80
+
81
+ def warn_about_method_overriding(method_name, field_name)
82
+ if instance_methods.include?(method_name.to_sym)
83
+ logger.warn("Method #{method_name} generated for the field #{field_name} " \
84
+ 'overrides already existing method')
85
+ end
86
+ end
87
+ end
88
+
89
+ def attribute_names
90
+ @attributes.keys
91
+ end
92
+
93
+ def has_attribute?(attr_name)
94
+ @attributes.key?(attr_name.to_sym)
95
+ end
96
+
97
+ def evaluate_default_value(val)
98
+ if val.respond_to?(:call)
99
+ val.call
100
+ elsif val.duplicable?
101
+ val.dup
102
+ else
103
+ val
104
+ end
105
+ end
106
+
107
+ attr_accessor :attributes
108
+
109
+ def write_attribute(name, value)
110
+ attributes[name.to_sym] = TypeCasting.cast_field(value, self.class.attributes[name.to_sym])
111
+ end
112
+
113
+ alias []= write_attribute
114
+
115
+ def read_attribute(name)
116
+ attributes[name.to_sym]
117
+ end
118
+ alias [] read_attribute
119
+
120
+ def set_created_at
121
+ self.created_at ||= DateTime.now.in_time_zone(Time.zone).iso8601 if timestamps_enabled?
122
+ end
123
+
124
+ def timestamps_enabled?
125
+ self.class.options[:timestamps] || (self.class.options[:timestamps].nil? && Config.timestamps)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aliyun
4
+ module Log
5
+ module Record
6
+ module Persistence
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def logstore_name
11
+ @logstore_name ||= options[:name] ||
12
+ base_class.name.split('::').last.underscore.pluralize
13
+ end
14
+
15
+ def logstore_name=(value)
16
+ if defined?(@logstore_name)
17
+ return if value == @logstore_name
18
+ end
19
+
20
+ @logstore_name = value
21
+ end
22
+
23
+ def project_name
24
+ unless @project_name
25
+ @project_name = options[:project] || Config.project
26
+ raise ProjectNameError, "project can't be empty" if @project_name.blank?
27
+ end
28
+ @project_name
29
+ end
30
+
31
+ def create_logstore(options = {})
32
+ Log.record_connection.get_logstore(project_name, logstore_name)
33
+ rescue ServerError => e
34
+ Log.record_connection.create_logstore(project_name, logstore_name, options)
35
+ end
36
+
37
+ def sync_index
38
+ has_index? ? update_index : create_index
39
+ end
40
+
41
+ def auto_load_schema
42
+ return if _schema_load
43
+
44
+ create_logstore
45
+ sync_index
46
+ self._schema_load = true
47
+ end
48
+
49
+ def has_index?
50
+ Log.record_connection.get_index(project_name, logstore_name)
51
+ true
52
+ rescue ServerError
53
+ false
54
+ end
55
+
56
+ def create(data, opts = {})
57
+ auto_load_schema
58
+ if data.is_a?(Array)
59
+ logs = data.map do |log_attr|
60
+ new(log_attr).save_array
61
+ end
62
+ res = Log.record_connection.put_log(project_name, logstore_name, logs, opts)
63
+ res.code == 200
64
+ else
65
+ new(data).save
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def evaluate_default_value(val)
72
+ if val.respond_to?(:call)
73
+ val.call
74
+ elsif val.duplicable?
75
+ val.dup
76
+ else
77
+ val
78
+ end
79
+ end
80
+
81
+ def field_indices
82
+ if options[:field_index] == false
83
+ attributes.select { |_, value| value[:index] == true }
84
+ else
85
+ attributes.reject { |_, value| value[:index] == false }
86
+ end
87
+ end
88
+
89
+ def create_index
90
+ Log.record_connection.create_index(
91
+ project_name,
92
+ logstore_name,
93
+ field_indices
94
+ )
95
+ end
96
+
97
+ def update_index
98
+ conf_res = Log.record_connection.get_index(project_name, logstore_name)
99
+ raw_conf = JSON.parse(conf_res)
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)
104
+ end
105
+ return if index_conf['keys'] == raw_conf['keys']
106
+
107
+ Log.record_connection.update_index(
108
+ project_name,
109
+ logstore_name,
110
+ index_conf['keys'].with_indifferent_access
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
132
+ end
133
+
134
+ def save
135
+ self.class.auto_load_schema
136
+ run_callbacks(:create) do
137
+ run_callbacks(:save) do
138
+ if valid?
139
+ res = Log.record_connection.put_log(
140
+ self.class.project_name,
141
+ self.class.logstore_name,
142
+ dump_attributes
143
+ )
144
+ res.code == 200
145
+ else
146
+ false
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ def save_array
153
+ run_callbacks(:create) do
154
+ run_callbacks(:save) do
155
+ validate! && dump_attributes
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aliyun
4
+ module Log
5
+ module Record
6
+ class Relation
7
+ def initialize(klass, opts = {})
8
+ @klass = klass
9
+ @opts = opts
10
+ @klass.auto_load_schema
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
54
+ end
55
+
56
+ def from(from)
57
+ ts = from.is_a?(Integer) ? from : from.to_time.to_i
58
+ @opts[:from] = ts
59
+ self
60
+ end
61
+
62
+ def to(to)
63
+ ts = to.is_a?(Integer) ? to : to.to_time.to_i
64
+ @opts[:to] = ts
65
+ self
66
+ end
67
+
68
+ def line(val)
69
+ @opts[:line] = val.to_i
70
+ self
71
+ end
72
+ alias limit line
73
+
74
+ def offset(val)
75
+ @opts[:offset] = val.to_i
76
+ self
77
+ end
78
+
79
+ def page(val)
80
+ @opts[:page] = val - 1 if val >= 1
81
+ self
82
+ end
83
+
84
+ def where(opts)
85
+ @opts.merge!(opts)
86
+ self
87
+ end
88
+
89
+ def search(*statement)
90
+ ql = statement_ql(*statement)
91
+ @opts[:search] = ql if ql.present?
92
+ self
93
+ end
94
+
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?
101
+ self
102
+ end
103
+
104
+ def query(opts = {})
105
+ @opts[:query] = opts
106
+ self
107
+ end
108
+
109
+ def count
110
+ query = @opts.dup
111
+ if query[:query].blank?
112
+ where_cond = query[:sql].split(/where /i)[1] if query[:sql].present?
113
+ query[:query] = "#{query[:search]}|SELECT COUNT(*) as count"
114
+ query[:query] = "#{query[:query]} WHERE #{where_cond}" if where_cond.present?
115
+ end
116
+ res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
117
+ res = JSON.parse(res.body)
118
+ res[0]['count'].to_i
119
+ end
120
+
121
+ def result
122
+ query = @opts.dup
123
+ if query[:page]
124
+ query[:line] ||= 100
125
+ query[:offset] = query[:page] * query[:line]
126
+ end
127
+ query[:query] = query[:search] || '*'
128
+ query[:query] = "#{query[:query]}|#{query[:sql]}" if query[:sql].present?
129
+ res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
130
+ JSON.parse(res)
131
+ end
132
+
133
+ def load
134
+ result.map do |json_attr|
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}"
203
+ end
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
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+ 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_datetime.iso8601
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
@@ -53,24 +53,21 @@ module Aliyun
53
53
 
54
54
  def do_request(verb, resources, payload)
55
55
  resource_path = get_resource_path(resources)
56
- headers = {}
57
- if verb == 'GET'
58
- headers = compact_headers
59
- headers['Authorization'] = signature(verb, resource_path, headers, payload)
60
- else
61
- headers = compact_headers(payload, resources[:is_pb])
62
- headers['Authorization'] = signature(verb, resource_path, headers)
63
- end
64
56
  request_options = {
65
57
  method: verb,
66
58
  url: get_request_url(resources),
67
- headers: headers,
68
59
  open_timeout: @config.open_timeout,
69
60
  read_timeout: @config.read_timeout
70
61
  }
71
62
  if verb == 'GET'
63
+ headers = compact_headers
64
+ headers['Authorization'] = signature(verb, resource_path, headers, payload)
65
+ request_options[:headers] = headers
72
66
  request_options[:url] = URI.escape(canonicalized_resource(request_options[:url], payload))
73
67
  else
68
+ headers = compact_headers(payload, resources[:is_pb])
69
+ headers['Authorization'] = signature(verb, resource_path, headers)
70
+ request_options[:headers] = headers
74
71
  payload = Zlib::Deflate.deflate(payload.encode) if resources[:is_pb]
75
72
  request_options[:payload] = payload
76
73
  end
@@ -86,7 +83,7 @@ module Aliyun
86
83
  end
87
84
 
88
85
  logger.debug("Received HTTP response, code: #{response.code}, headers: " \
89
- "#{response.headers}, body: #{response.body}")
86
+ "#{response.headers}, body: #{response.body.force_encoding('UTF-8')}")
90
87
 
91
88
  response
92
89
  end
@@ -104,7 +101,6 @@ module Aliyun
104
101
  if is_pb
105
102
  compressed = Zlib::Deflate.deflate(body.encode)
106
103
  headers['Content-Length'] = compressed.bytesize.to_s
107
- # 日志内容包含的日志必须小于3MB和4096条。
108
104
  raise 'content length is larger than 3MB' if headers['Content-Length'].to_i > 3_145_728
109
105
 
110
106
  headers['Content-MD5'] = Digest::MD5.hexdigest(compressed).upcase
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aliyun
4
4
  module Log
5
- VERSION = '0.1.3'
5
+ VERSION = '0.2.9'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aliyun-log
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.9
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-05 00:00:00.000000000 Z
11
+ date: 2020-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: protobuf
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,7 +114,7 @@ dependencies:
86
114
  - - "~>"
87
115
  - !ruby/object:Gem::Version
88
116
  version: '3.0'
89
- description: Aliyun Log SDK for Ruby 阿里云日志服务(SLS) Ruby SDK, 目前仅实现基于Restfull部分接口
117
+ description: Aliyun Log SDK for Ruby 阿里云日志服务(SLS) Ruby SDK, 目前仅实现基于Restfull部分接口和简单Model映射
90
118
  email:
91
119
  - yingce@live.com
92
120
  executables: []
@@ -103,6 +131,14 @@ files:
103
131
  - lib/aliyun/log/project.rb
104
132
  - lib/aliyun/log/protobuf.rb
105
133
  - lib/aliyun/log/protocol.rb
134
+ - lib/aliyun/log/record.rb
135
+ - lib/aliyun/log/record/exception.rb
136
+ - lib/aliyun/log/record/field.rb
137
+ - lib/aliyun/log/record/persistence.rb
138
+ - lib/aliyun/log/record/relation.rb
139
+ - lib/aliyun/log/record/scope_registry.rb
140
+ - lib/aliyun/log/record/scoping.rb
141
+ - lib/aliyun/log/record/type_casting.rb
106
142
  - lib/aliyun/log/request.rb
107
143
  - lib/aliyun/log/server_error.rb
108
144
  - lib/aliyun/version.rb