aliyun-log 0.1.3 → 0.2.0

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: 4cc759f537d07276e59933f681a0014ae89066e65c98b236c70e541f52173d85
4
+ data.tar.gz: a3fcb9eff51e48f11b3b578e47cbfc878d3796f0f9c1b0ad5ffa0638f9024ff6
5
5
  SHA512:
6
- metadata.gz: f22410d5c16d83e0b54f9435301a17b4ec75ff72fbca11c583a95c0f40a8fa734311da4974eb7b0f22fad03b5b0f129bccf9b3f0ae6cdf91800500002bd1a476
7
- data.tar.gz: ddc6c2fa7ad08f0cadeade94268997eae1241ade19b046a9439a4a52cf4e4735bbbc4936835139a5b479ef4d2b07b8e220e4b97b20294f8f5260cee86d54f972
6
+ metadata.gz: e7a836e5dc43c44fc22a22304fa1a140d0b230fde3047271fb65a70f8c866dfc0ec014e86b12666f2ce9cb7de73487038cab63698e5a21a6a423d7f69f8a3419
7
+ data.tar.gz: 46e1132a1d9a81af0e2279a6f847abe73f2c23265a7aea018f289b63b85a6bdfa6a5415031c1df45087c8aca8f1e61b02e22a6ce76f8e4e0401278673893962e
@@ -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
@@ -9,9 +9,11 @@ module Aliyun
9
9
  @read_timeout = 120
10
10
  @log_file = 'aliyun_log.log'
11
11
  @log_level = Logger::DEBUG
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
@@ -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.compact.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].blank?
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].blank?
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,122 @@
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/relation'
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, :text if Config.timestamps
31
+
32
+ define_model_callbacks :save, :create, :initialize
33
+
34
+ after_initialize :set_created_at
35
+ end
36
+
37
+ include Field
38
+ include Persistence
39
+
40
+ include ActiveModel::AttributeMethods
41
+
42
+ module ClassMethods
43
+ def logstore(options = {})
44
+ if options[:timestamps] && !Config.timestamps
45
+ field :created_at, :text
46
+ elsif options[:timestamps] == false && Config.timestamps
47
+ remove_field :created_at
48
+ 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)
69
+ end
70
+ end
71
+
72
+ def initialize(attrs = {})
73
+ run_callbacks :initialize do
74
+ @new_record = true
75
+ @attributes ||= {}
76
+
77
+ attrs_with_defaults = self.class.attributes.each_with_object({}) do |(attribute, options), res|
78
+ res[attribute] = if attrs.key?(attribute)
79
+ attrs[attribute]
80
+ elsif options.key?(:default)
81
+ evaluate_default_value(options[:default])
82
+ end
83
+ end
84
+
85
+ attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
86
+
87
+ attrs_with_defaults.merge(attrs_virtual).each do |key, value|
88
+ if respond_to?("#{key}=")
89
+ send("#{key}=", value)
90
+ else
91
+ raise UnknownAttributeError, "unknown attribute '#{key}' for #{@record.class}."
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def inspect
98
+ inspection = if defined?(@attributes) && @attributes
99
+ self.class.attributes.keys.collect do |name|
100
+ "#{name}: #{attribute_for_inspect(name)}" if has_attribute?(name)
101
+ end.compact.join(', ')
102
+ else
103
+ 'not initialized'
104
+ end
105
+
106
+ "#<#{self.class} #{inspection}>"
107
+ end
108
+
109
+ def attribute_for_inspect(attr_name)
110
+ value = read_attribute(attr_name)
111
+
112
+ if value.is_a?(String) && value.length > 50
113
+ "#{value[0, 50]}...".inspect
114
+ elsif value.is_a?(Date) || value.is_a?(Time)
115
+ %("#{value.to_s(:db)}")
116
+ else
117
+ value.inspect
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,9 @@
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
+ end
8
+ end
9
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+ require "active_support"
3
+ require "active_support/time"
4
+ require "active_support/core_ext"
5
+ require "active_model"
6
+
7
+ module Aliyun
8
+ module Log
9
+ module Record
10
+ module Field
11
+ extend ActiveSupport::Concern
12
+
13
+ # Types allowed in indexes:
14
+ PERMITTED_KEY_TYPES = %i[
15
+ text
16
+ long
17
+ double
18
+ json
19
+ ].freeze
20
+
21
+ DEFAULT_INDEX_TOKEN = ", '\";=()[]{}?@&<>/:\n\t\r".split('')
22
+
23
+ included do
24
+ class_attribute :attributes, instance_accessor: false, default: {}
25
+ end
26
+
27
+ module ClassMethods
28
+ def field(name, type = :text, options = {})
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
+ named = name.to_s
33
+ self.attributes = attributes.merge(name => { type: type }.merge(options))
34
+
35
+ warn_about_method_overriding(name, name)
36
+ warn_about_method_overriding("#{named}=", name)
37
+ warn_about_method_overriding("#{named}?", name)
38
+
39
+ define_attribute_method(name) # Dirty API
40
+
41
+ generated_methods.module_eval do
42
+ define_method(named) { read_attribute(named) }
43
+ define_method("#{named}?") do
44
+ value = read_attribute(named)
45
+ case value
46
+ when true then true
47
+ when false, nil then false
48
+ else
49
+ !value.nil?
50
+ end
51
+ end
52
+ define_method("#{named}=") { |value| write_attribute(named, value) }
53
+ end
54
+ end
55
+
56
+ def remove_field(field)
57
+ field = field.to_sym
58
+ attributes.delete(field) || raise('No such field')
59
+
60
+ undefine_attribute_methods
61
+ define_attribute_methods attributes.keys
62
+
63
+ generated_methods.module_eval do
64
+ remove_method field
65
+ remove_method :"#{field}="
66
+ remove_method :"#{field}?"
67
+ remove_method :"#{field}_before_type_cast"
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
+ Common::Logging.logger.warn("Method #{method_name} generated for the field #{field_name} overrides already existing method")
84
+ end
85
+ end
86
+ end
87
+
88
+ def attribute_names
89
+ @attributes.keys
90
+ end
91
+
92
+ def has_attribute?(attr_name)
93
+ @attributes.key?(attr_name.to_sym)
94
+ end
95
+
96
+ def evaluate_default_value(val)
97
+ if val.respond_to?(:call)
98
+ val.call
99
+ elsif val.duplicable?
100
+ val.dup
101
+ else
102
+ val
103
+ end
104
+ end
105
+
106
+ attr_accessor :attributes
107
+
108
+ def write_attribute(name, value)
109
+ attributes[name.to_sym] = value
110
+ end
111
+
112
+ alias []= write_attribute
113
+
114
+ def read_attribute(name)
115
+ attributes[name.to_sym]
116
+ end
117
+ alias [] read_attribute
118
+
119
+ def set_created_at
120
+ self.created_at ||= DateTime.now.in_time_zone(Time.zone).to_s if timestamps_enabled?
121
+ end
122
+
123
+ def timestamps_enabled?
124
+ self.class.options[:timestamps] || (self.class.options[:timestamps].nil? && Config.timestamps)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,138 @@
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] || base_class.name.split('::').last.downcase.pluralize
12
+ end
13
+
14
+ def logstore_name=(value)
15
+ if defined?(@logstore_name)
16
+ return if value == @logstore_name
17
+ end
18
+
19
+ @logstore_name = value
20
+ end
21
+
22
+ def project_name
23
+ unless @project_name
24
+ @project_name = options[:project] || Config.project
25
+ raise ProjectNameError, "project can't be empty" if @project_name.blank?
26
+ end
27
+ @project_name
28
+ end
29
+
30
+ def create_logstore(options = {})
31
+ Log.record_connection.get_logstore(project_name, logstore_name)
32
+ rescue ServerError => e
33
+ Log.record_connection.create_logstore(project_name, logstore_name, options)
34
+ end
35
+
36
+ def sync_index
37
+ has_index? ? update_index : create_index
38
+ end
39
+
40
+ def auto_load_schema
41
+ return if _schema_load
42
+
43
+ create_logstore
44
+ sync_index
45
+ self._schema_load = true
46
+ end
47
+
48
+ def has_index?
49
+ Log.record_connection.get_index(project_name, logstore_name)
50
+ true
51
+ rescue ServerError
52
+ false
53
+ end
54
+
55
+ def create(data, opts = {})
56
+ auto_load_schema
57
+ if data.is_a?(Array)
58
+ logs = []
59
+ data.each do |log_attr|
60
+ logs << 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] == true
83
+ attributes.reject { |_, value| value[:index] == false }
84
+ else
85
+ attributes.select { |_, value| value[:index] == true }
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.dup
101
+ field_indices.each do |k, v|
102
+ index_conf['keys'][k.to_s] ||= v
103
+ end
104
+ return if index_conf['keys'] == raw_conf['keys']
105
+
106
+ Log.record_connection.update_index(
107
+ project_name,
108
+ logstore_name,
109
+ index_conf['keys']
110
+ )
111
+ end
112
+ end
113
+
114
+ def save
115
+ self.class.auto_load_schema
116
+ run_callbacks(:create) do
117
+ run_callbacks(:save) do
118
+ if valid?
119
+ res = Log.record_connection.put_log(self.class.project_name, self.class.logstore_name, attributes)
120
+ res.code == 200
121
+ else
122
+ false
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def save_array
129
+ run_callbacks(:create) do
130
+ run_callbacks(:save) do
131
+ validate! && attributes
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,98 @@
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
+ @opts[:search] ||= '*'
12
+ end
13
+
14
+ def from(from)
15
+ ts = from.is_a?(Integer) ? from : from.to_time.to_i
16
+ @opts[:from] = ts
17
+ self
18
+ end
19
+
20
+ def to(to)
21
+ ts = to.is_a?(Integer) ? to : to.to_time.to_i
22
+ @opts[:to] = ts
23
+ self
24
+ end
25
+
26
+ def line(val)
27
+ @opts[:line] = val.to_i
28
+ self
29
+ end
30
+ alias limit line
31
+
32
+ def offset(val)
33
+ @opts[:offset] = val.to_i
34
+ self
35
+ end
36
+
37
+ def page(val)
38
+ @opts[:page] = val - 1 if val >= 1
39
+ self
40
+ end
41
+
42
+ def where(opts)
43
+ @opts.merge!(opts)
44
+ self
45
+ end
46
+
47
+ def search(str)
48
+ @opts[:search] = str
49
+ self
50
+ end
51
+
52
+ def sql(str)
53
+ @opts[:sql] = str
54
+ self
55
+ end
56
+
57
+ def query(opts = {})
58
+ @opts[:query] = opts
59
+ self
60
+ end
61
+
62
+ def count
63
+ query = @opts.dup
64
+ if query[:query].blank?
65
+ where_cond = query[:sql].split(/where /i)[1] if query[:sql].present?
66
+ query[:query] = "#{query[:search]}|SELECT COUNT(*) as count"
67
+ query[:query] = "#{query[:query]} WHERE #{where_cond}" if where_cond.present?
68
+ end
69
+ res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
70
+ res = JSON.parse(res.body)
71
+ res[0]['count'].to_i
72
+ end
73
+
74
+ def result
75
+ query = @opts.dup
76
+ if query[:page]
77
+ query[:line] ||= 100
78
+ query[:offset] = query[:page] * query[:line]
79
+ end
80
+ query[:query] = query[:search]
81
+ query[:query] = "#{query[:query]}|#{query[:sql]}" if query[:sql].present?
82
+ res = Log.record_connection.get_logs(@klass.project_name, @klass.logstore_name, query)
83
+ JSON.parse(res)
84
+ end
85
+
86
+ def load
87
+ result.map do |json_attr|
88
+ attrs = {}
89
+ @klass.attributes.keys.each do |k, _|
90
+ attrs[k] = json_attr[k.to_s]
91
+ end
92
+ @klass.new(attrs)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ 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
@@ -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.0'
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.0
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-09 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
@@ -103,6 +131,11 @@ 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
106
139
  - lib/aliyun/log/request.rb
107
140
  - lib/aliyun/log/server_error.rb
108
141
  - lib/aliyun/version.rb