thinkingdata-ruby 1.2.1 → 2.0.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 +4 -4
- data/.gitignore +58 -58
- data/CHANGELOG.md +21 -15
- data/Gemfile +7 -7
- data/LICENSE +201 -201
- data/README.md +11 -10
- data/demo/demo.rb +104 -105
- data/lib/thinkingdata-ruby/{tracker.rb → td_analytics.rb} +495 -409
- data/lib/thinkingdata-ruby/{batch_consumer.rb → td_batch_consumer.rb} +142 -118
- data/lib/thinkingdata-ruby/{debug_consumer.rb → td_debug_consumer.rb} +73 -67
- data/lib/thinkingdata-ruby/{errors.rb → td_errors.rb} +37 -29
- data/lib/thinkingdata-ruby/{logger_consumer.rb → td_logger_consumer.rb} +76 -65
- data/lib/thinkingdata-ruby/td_version.rb +3 -0
- data/lib/thinkingdata-ruby.rb +5 -5
- data/thinkingdata-ruby.gemspec +16 -16
- metadata +12 -12
- data/lib/thinkingdata-ruby/version.rb +0 -3
@@ -1,118 +1,142 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'net/http'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module ThinkingData
|
6
|
+
##
|
7
|
+
# Upload data by http
|
8
|
+
class TDBatchConsumer
|
9
|
+
|
10
|
+
# buffer count
|
11
|
+
DEFAULT_LENGTH = 20
|
12
|
+
MAX_LENGTH = 2000
|
13
|
+
|
14
|
+
##
|
15
|
+
# Init batch consumer
|
16
|
+
def initialize(server_url, app_id, max_buffer_length = DEFAULT_LENGTH)
|
17
|
+
@server_uri = URI.parse(server_url)
|
18
|
+
@server_uri.path = '/sync_server'
|
19
|
+
@app_id = app_id
|
20
|
+
@compress = true
|
21
|
+
@max_length = [max_buffer_length, MAX_LENGTH].min
|
22
|
+
@buffers = []
|
23
|
+
TDLog.info("TDBatchConsumer init success. ServerUrl: #{server_url}, appId: #{app_id}")
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# http request compress
|
28
|
+
# @param compress [Boolean] compress or not
|
29
|
+
# @deprecated please use: set_compress
|
30
|
+
def _set_compress(compress)
|
31
|
+
@compress = compress
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# http request compress
|
36
|
+
# @param compress [Boolean] compress or not
|
37
|
+
def set_compress(compress)
|
38
|
+
@compress = compress
|
39
|
+
end
|
40
|
+
|
41
|
+
def add(message)
|
42
|
+
TDLog.info("Enqueue data to buffer. buffer size: #{@buffers.length}, data: #{message}")
|
43
|
+
@buffers << message
|
44
|
+
flush if @buffers.length >= @max_length
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
flush
|
49
|
+
TDLog.info("TDBatchConsumer close.")
|
50
|
+
end
|
51
|
+
|
52
|
+
def flush
|
53
|
+
TDLog.info("TDBatchConsumer flush data.")
|
54
|
+
begin
|
55
|
+
@buffers.each_slice(@max_length) do |chunk|
|
56
|
+
if @compress
|
57
|
+
wio = StringIO.new("w")
|
58
|
+
gzip_io = Zlib::GzipWriter.new(wio)
|
59
|
+
gzip_io.write(chunk.to_json)
|
60
|
+
gzip_io.close
|
61
|
+
data = wio.string
|
62
|
+
else
|
63
|
+
data = chunk.to_json
|
64
|
+
end
|
65
|
+
compress_type = @compress ? 'gzip' : 'none'
|
66
|
+
headers = {'Content-Type' => 'application/plaintext',
|
67
|
+
'appid' => @app_id,
|
68
|
+
'compress' => compress_type,
|
69
|
+
'TE-Integration-Type'=>'Ruby',
|
70
|
+
'TE-Integration-Version'=>ThinkingData::VERSION,
|
71
|
+
'TE-Integration-Count'=>@buffers.count,
|
72
|
+
'TA_Integration-Extra'=>'batch'}
|
73
|
+
request = CaseSensitivePost.new(@server_uri.request_uri, headers)
|
74
|
+
request.body = data
|
75
|
+
|
76
|
+
TDLog.info("Send data, request: #{data}")
|
77
|
+
begin
|
78
|
+
response_code, response_body = _request(@server_uri, request)
|
79
|
+
TDLog.info("Send data, response: #{response_body}")
|
80
|
+
rescue => e
|
81
|
+
raise ConnectionError.new("Could not connect to TE server, with error \"#{e.message}\".")
|
82
|
+
end
|
83
|
+
|
84
|
+
result = {}
|
85
|
+
if response_code.to_i == 200
|
86
|
+
begin
|
87
|
+
result = JSON.parse(response_body.to_s)
|
88
|
+
rescue JSON::JSONError
|
89
|
+
raise ServerError.new("Could not interpret TE server response: '#{response_body}'")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if result['code'] != 0
|
94
|
+
raise ServerError.new("Could not write to TE, server responded with #{response_code} returning: '#{response_body}'")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
@buffers = []
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def _request(uri, request)
|
105
|
+
client = Net::HTTP.new(uri.host, uri.port)
|
106
|
+
client.use_ssl = uri.scheme === 'https' ? true : false
|
107
|
+
client.open_timeout = 10
|
108
|
+
client.continue_timeout = 10
|
109
|
+
client.read_timeout = 10
|
110
|
+
client.ssl_timeout = 10
|
111
|
+
|
112
|
+
response = client.request(request)
|
113
|
+
[response.code, response.body]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Private class. Send data tools
|
119
|
+
class CaseSensitivePost < Net::HTTP::Post
|
120
|
+
def initialize_http_header(headers)
|
121
|
+
@header = {}
|
122
|
+
headers.each{|k,v| @header[k.to_s] = [v] }
|
123
|
+
end
|
124
|
+
|
125
|
+
def [](name)
|
126
|
+
@header[name.to_s]
|
127
|
+
end
|
128
|
+
|
129
|
+
def []=(name, val)
|
130
|
+
if val
|
131
|
+
@header[name.to_s] = [val]
|
132
|
+
else
|
133
|
+
@header.delete(name.to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def capitalize(name)
|
138
|
+
name
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -1,67 +1,73 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'net/http'
|
3
|
-
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module ThinkingData
|
5
|
+
##
|
6
|
+
# The data is reported one by one, and when an error occurs, the log will be printed on the console.
|
7
|
+
class TDDebugConsumer
|
8
|
+
|
9
|
+
##
|
10
|
+
# Init debug consumer
|
11
|
+
# @param server_url: server url
|
12
|
+
# @param app_id: app id
|
13
|
+
# @param write_data: is write data to TE
|
14
|
+
# @param device_id: device id
|
15
|
+
def initialize(server_url, app_id, write_data = true, device_id: nil)
|
16
|
+
@server_uri = URI.parse(server_url)
|
17
|
+
@server_uri.path = '/data_debug'
|
18
|
+
@app_id = app_id
|
19
|
+
@write_data = write_data
|
20
|
+
@device_id = device_id
|
21
|
+
TDLog.info("TDDebugConsumer init success. ServerUrl: #{server_url}, appId: #{app_id}, deviceId: #{device_id}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(message)
|
25
|
+
msg_json_str = message.to_json
|
26
|
+
TDLog.info("Send data, request: #{msg_json_str}")
|
27
|
+
headers = {
|
28
|
+
'TE-Integration-Type'=>'Ruby',
|
29
|
+
'TE-Integration-Version'=>ThinkingData::VERSION,
|
30
|
+
'TE-Integration-Count'=>'1',
|
31
|
+
'TA_Integration-Extra'=>'debug'
|
32
|
+
}
|
33
|
+
form_data = {"data" => msg_json_str, "appid" => @app_id, "dryRun" => @write_data ? "0" : "1", "source" => "server"}
|
34
|
+
@device_id.is_a?(String) ? form_data["deviceId"] = @device_id : nil
|
35
|
+
|
36
|
+
begin
|
37
|
+
response_code, response_body = request(@server_uri, form_data,headers)
|
38
|
+
TDLog.info("Send data, response: #{response_body}")
|
39
|
+
rescue => e
|
40
|
+
raise ConnectionError.new("Could not connect to TE server, with error \"#{e.message}\".")
|
41
|
+
end
|
42
|
+
|
43
|
+
result = {}
|
44
|
+
if response_code.to_i == 200
|
45
|
+
begin
|
46
|
+
result = JSON.parse(response_body.to_s)
|
47
|
+
rescue JSON::JSONError
|
48
|
+
raise ServerError.new("Could not interpret TE server response: '#{response_body}'")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if result['errorLevel'] != 0
|
53
|
+
raise ServerError.new("Could not write to TE, server responded with #{response_code} returning: '#{response_body}'")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def request(uri, form_data,headers)
|
58
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
59
|
+
request.set_form_data(form_data)
|
60
|
+
|
61
|
+
client = Net::HTTP.new(uri.host, uri.port)
|
62
|
+
client.use_ssl = uri.scheme === 'https' ? true : false
|
63
|
+
client.open_timeout = 10
|
64
|
+
client.continue_timeout = 10
|
65
|
+
client.read_timeout = 10
|
66
|
+
client.ssl_timeout = 10
|
67
|
+
|
68
|
+
response = client.request(request)
|
69
|
+
[response.code, response.body]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -1,30 +1,38 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
module ThinkingData
|
2
|
+
##
|
3
|
+
# SDK error
|
4
|
+
TDAnalyticsError = Class.new(StandardError)
|
5
|
+
|
6
|
+
##
|
7
|
+
# SDK error: illegal parameter
|
8
|
+
IllegalParameterError = Class.new(TDAnalyticsError)
|
9
|
+
|
10
|
+
##
|
11
|
+
# SDK error: connection error
|
12
|
+
ConnectionError = Class.new(TDAnalyticsError)
|
13
|
+
|
14
|
+
##
|
15
|
+
# SDK error: server error
|
16
|
+
ServerError = Class.new(TDAnalyticsError)
|
17
|
+
|
18
|
+
##
|
19
|
+
# Error handler
|
20
|
+
#
|
21
|
+
# e.g.
|
22
|
+
# class MyErrorHandler < ThinkingData::ErrorHandler
|
23
|
+
# def handle(error)
|
24
|
+
# puts error
|
25
|
+
# raise error
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# my_error_handler = MyErrorHandler.new
|
30
|
+
# tracker = ThinkingData::TDAnalytics.new(consumer, my_error_handler)
|
31
|
+
class TDErrorHandler
|
32
|
+
##
|
33
|
+
# Override #handle to customize error handling
|
34
|
+
def handle(error)
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
30
38
|
end
|
@@ -1,66 +1,77 @@
|
|
1
|
-
require 'logger'
|
2
|
-
require 'thinkingdata-ruby/
|
3
|
-
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
|
1
|
+
require 'logger'
|
2
|
+
require 'thinkingdata-ruby/td_errors'
|
3
|
+
|
4
|
+
module ThinkingData
|
5
|
+
|
6
|
+
##
|
7
|
+
# Dismantle the header and save it under another name
|
8
|
+
class HeadlessLogger < Logger
|
9
|
+
def initialize(logdev, shift_age = 0, shift_size = 1048576)
|
10
|
+
super(nil)
|
11
|
+
if logdev
|
12
|
+
@logdev = HeadlessLogger::LogDevice.new(logdev, shift_age: shift_age, shift_size: shift_size)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class LogDevice < ::Logger::LogDevice
|
17
|
+
def add_log_header(file); end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Write data to file, it works with LogBus
|
23
|
+
class TDLoggerConsumer
|
24
|
+
|
25
|
+
##
|
26
|
+
# Init logger consumer
|
27
|
+
# @param log_path: log file's path
|
28
|
+
# @param mode: file rotate mode
|
29
|
+
# @param prefix: file prefix
|
30
|
+
def initialize(log_path='.', mode='daily', prefix:'te.log')
|
31
|
+
case mode
|
32
|
+
when 'hourly'
|
33
|
+
@suffix_mode = '%Y-%m-%d-%H'
|
34
|
+
when 'daily'
|
35
|
+
@suffix_mode = '%Y-%m-%d'
|
36
|
+
else
|
37
|
+
raise IllegalParameterError.new("#{mode} is unsupported for LoggerConsumer. Replaced it by daily or hourly")
|
38
|
+
end
|
39
|
+
|
40
|
+
raise IllegalParameterError.new("prefix couldn't be empty") if prefix.nil? || prefix.length == 0
|
41
|
+
|
42
|
+
@current_suffix = Time.now.strftime(@suffix_mode)
|
43
|
+
@log_path = log_path
|
44
|
+
@full_prefix = "#{log_path}/#{prefix}"
|
45
|
+
TDLog.info("TDLoggerConsumer init success. LogPath: #{log_path}")
|
46
|
+
_reset
|
47
|
+
end
|
48
|
+
|
49
|
+
def add(msg)
|
50
|
+
unless Time.now.strftime(@suffix_mode) == @current_suffix
|
51
|
+
@logger.close
|
52
|
+
@current_suffix = Time.now.strftime(@suffix_mode)
|
53
|
+
_reset
|
54
|
+
end
|
55
|
+
msg_json_str = msg.to_json
|
56
|
+
TDLog.info("Write data to file: #{msg_json_str}")
|
57
|
+
@logger.info(msg_json_str)
|
58
|
+
end
|
59
|
+
|
60
|
+
def close
|
61
|
+
@logger.close
|
62
|
+
TDLog.info("TDLoggerConsumer close.")
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def _reset
|
68
|
+
Dir::mkdir(@log_path) unless Dir::exist?(@log_path)
|
69
|
+
@logger = HeadlessLogger.new("#{@full_prefix}.#{@current_suffix}")
|
70
|
+
@logger.level = HeadlessLogger::INFO
|
71
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
72
|
+
"#{msg}\n"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
66
77
|
end
|
data/lib/thinkingdata-ruby.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require 'thinkingdata-ruby/
|
2
|
-
require 'thinkingdata-ruby/
|
3
|
-
require 'thinkingdata-ruby/
|
4
|
-
require 'thinkingdata-ruby/
|
5
|
-
require 'thinkingdata-ruby/
|
1
|
+
require 'thinkingdata-ruby/td_logger_consumer'
|
2
|
+
require 'thinkingdata-ruby/td_debug_consumer'
|
3
|
+
require 'thinkingdata-ruby/td_batch_consumer'
|
4
|
+
require 'thinkingdata-ruby/td_analytics'
|
5
|
+
require 'thinkingdata-ruby/td_errors'
|