aliyun-log 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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