fluent-plugin-bigquery 0.0.3 → 0.0.4
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/.travis.yml +8 -0
- data/README.md +69 -1
- data/fluent-plugin-bigquery.gemspec +1 -0
- data/lib/fluent/plugin/bigquery/version.rb +1 -1
- data/lib/fluent/plugin/out_bigquery.rb +51 -8
- data/test/helper.rb +2 -0
- data/test/plugin/test_out_bigquery.rb +335 -0
- data/test/plugin/testdata/apache.schema +98 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91a4bfe02680f2079998d36ab098c5498bdaea04
|
4
|
+
data.tar.gz: 234135f11479a6d364d1c5d0f58d90cfe7d72717
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1e3df653dca491e163824f57e6d3d0932314ef59b2cc0cf36caa77f9007df624b183cd2b700caf5c02bd6a477322d136e44c14bed15f44c8c201eb9fb43838e
|
7
|
+
data.tar.gz: d20de3a18a7507fb5532480cd51704e23e79740d12a58bde482824095740681b10ede39a9e0611e878bba6e609078dc406e5e983b5ea5160e83b0632a4c83542
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# fluent-plugin-bigquery
|
2
2
|
|
3
|
-
Fluentd output plugin to load/insert data into Google BigQuery.
|
3
|
+
[Fluentd](http://fluentd.org) output plugin to load/insert data into Google BigQuery.
|
4
4
|
|
5
5
|
* insert data over streaming inserts
|
6
6
|
* for continuous real-time insertions, under many limitations
|
@@ -104,6 +104,7 @@ Important options for high rate events are:
|
|
104
104
|
### Authentication
|
105
105
|
|
106
106
|
There are two methods supported to fetch access token for the service account.
|
107
|
+
|
107
108
|
1. Public-Private key pair
|
108
109
|
2. Predefined access token (Compute Engine only)
|
109
110
|
|
@@ -135,6 +136,73 @@ Compute Engine instance, then you can configure fluentd like this.
|
|
135
136
|
</match>
|
136
137
|
```
|
137
138
|
|
139
|
+
### Table schema
|
140
|
+
|
141
|
+
There are two methods to describe the schema of the target table.
|
142
|
+
|
143
|
+
1. List fields in fluent.conf
|
144
|
+
2. Load a schema file in JSON.
|
145
|
+
|
146
|
+
The examples above use the first method. In this method,
|
147
|
+
you can also specify nested fields by prefixing their belonging record fields.
|
148
|
+
|
149
|
+
```apache
|
150
|
+
<match dummy>
|
151
|
+
type bigquery
|
152
|
+
|
153
|
+
...
|
154
|
+
|
155
|
+
time_format %s
|
156
|
+
time_field time
|
157
|
+
|
158
|
+
field_integer time,response.status,response.bytes
|
159
|
+
field_string request.vhost,request.path,request.method,request.protocol,request.agent,request.referer,remote.host,remote.ip,remote.user
|
160
|
+
field_float request.time
|
161
|
+
field_boolean request.bot_access,request.loginsession
|
162
|
+
</match>
|
163
|
+
```
|
164
|
+
|
165
|
+
This schema accepts structured JSON data like:
|
166
|
+
|
167
|
+
```json
|
168
|
+
{
|
169
|
+
"request":{
|
170
|
+
"time":1391748126.7000976,
|
171
|
+
"vhost":"www.example.com",
|
172
|
+
"path":"/",
|
173
|
+
"method":"GET",
|
174
|
+
"protocol":"HTTP/1.1",
|
175
|
+
"agent":"HotJava",
|
176
|
+
"bot_access":false
|
177
|
+
},
|
178
|
+
"remote":{ "ip": "192.0.2.1" },
|
179
|
+
"response":{
|
180
|
+
"status":200,
|
181
|
+
"bytes":1024
|
182
|
+
}
|
183
|
+
}
|
184
|
+
```
|
185
|
+
|
186
|
+
The second method is to specify a path to a BigQuery schema file instead of listing fields. In this case, your fluent.conf looks like:
|
187
|
+
|
188
|
+
```apache
|
189
|
+
<match dummy>
|
190
|
+
type bigquery
|
191
|
+
|
192
|
+
...
|
193
|
+
|
194
|
+
time_format %s
|
195
|
+
time_field time
|
196
|
+
|
197
|
+
schema_path /path/to/httpd.schema
|
198
|
+
field_integer time
|
199
|
+
</match>
|
200
|
+
```
|
201
|
+
where /path/to/httpd.schema is a path to the JSON-encoded schema file which you used for creating the table on BigQuery.
|
202
|
+
|
203
|
+
NOTE: Since JSON does not define how to encode data of TIMESTAMP type,
|
204
|
+
you are still recommended to specify JSON types for TIMESTAMP fields as "time" field does in the example.
|
205
|
+
|
138
206
|
|
139
207
|
### patches
|
140
208
|
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rr"
|
22
23
|
spec.add_runtime_dependency "google-api-client", "~> 0.7.1"
|
23
24
|
spec.add_runtime_dependency "fluentd"
|
24
25
|
spec.add_runtime_dependency "fluent-mixin-plaintextformatter", '>= 0.2.1'
|
@@ -63,6 +63,7 @@ module Fluent
|
|
63
63
|
config_param :table, :string, :default => nil
|
64
64
|
config_param :tables, :string, :default => nil
|
65
65
|
|
66
|
+
config_param :schema_path, :string, :default => nil
|
66
67
|
config_param :field_string, :string, :default => nil
|
67
68
|
config_param :field_integer, :string, :default => nil
|
68
69
|
config_param :field_float, :string, :default => nil
|
@@ -117,6 +118,7 @@ module Fluent
|
|
117
118
|
|
118
119
|
def initialize
|
119
120
|
super
|
121
|
+
require 'json'
|
120
122
|
require 'google/api_client'
|
121
123
|
require 'google/api_client/client_secrets'
|
122
124
|
require 'google/api_client/auth/installed_app'
|
@@ -137,13 +139,16 @@ module Fluent
|
|
137
139
|
raise Fluent::ConfigError, "unrecognized 'auth_method': #{@auth_method}"
|
138
140
|
end
|
139
141
|
|
140
|
-
if (!@table && !@tables) || (@table && @
|
142
|
+
if (!@table && !@tables) || (@table && @tables)
|
141
143
|
raise Fluent::ConfigError, "'table' or 'tables' must be specified, and both are invalid"
|
142
144
|
end
|
143
145
|
|
144
146
|
@tablelist = @tables ? @tables.split(',') : [@table]
|
145
147
|
|
146
148
|
@fields = RecordSchema.new
|
149
|
+
if @schema_path
|
150
|
+
@fields.load_schema(JSON.parse(File.read(@schema_path)))
|
151
|
+
end
|
147
152
|
if @field_string
|
148
153
|
@field_string.split(',').each do |fieldname|
|
149
154
|
@fields.register_field fieldname, :string
|
@@ -171,6 +176,17 @@ module Fluent
|
|
171
176
|
end
|
172
177
|
end
|
173
178
|
@timef = TimeFormatter.new(@time_format, @localtime)
|
179
|
+
|
180
|
+
if @time_field
|
181
|
+
keys = @time_field.split('.')
|
182
|
+
last_key = keys.pop
|
183
|
+
@add_time_field = lambda {|record, time|
|
184
|
+
keys.inject(record) { |h, k| h[k] ||= {} }[last_key] = @timef.format(time)
|
185
|
+
record
|
186
|
+
}
|
187
|
+
else
|
188
|
+
@add_time_field = lambda {|record, time| record }
|
189
|
+
end
|
174
190
|
end
|
175
191
|
|
176
192
|
def start
|
@@ -260,11 +276,7 @@ module Fluent
|
|
260
276
|
super
|
261
277
|
buf = ''
|
262
278
|
es.each do |time, record|
|
263
|
-
row =
|
264
|
-
@fields.format(record.merge({@time_field => @timef.format(time)}))
|
265
|
-
else
|
266
|
-
@fields.format(record)
|
267
|
-
end
|
279
|
+
row = @fields.format(@add_time_field.call(record, time))
|
268
280
|
buf << {"json" => row}.to_msgpack unless row.empty?
|
269
281
|
end
|
270
282
|
buf
|
@@ -350,12 +362,24 @@ module Fluent
|
|
350
362
|
end
|
351
363
|
end
|
352
364
|
|
365
|
+
class TimestampFieldSchema < FieldSchema
|
366
|
+
def type
|
367
|
+
:timestamp
|
368
|
+
end
|
369
|
+
|
370
|
+
def format(value)
|
371
|
+
value
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
353
375
|
class RecordSchema < FieldSchema
|
354
376
|
FIELD_TYPES = {
|
355
377
|
:string => StringFieldSchema,
|
356
378
|
:integer => IntegerFieldSchema,
|
357
379
|
:float => FloatFieldSchema,
|
358
|
-
:boolean => BooleanFieldSchema
|
380
|
+
:boolean => BooleanFieldSchema,
|
381
|
+
:timestamp => TimestampFieldSchema,
|
382
|
+
:record => RecordSchema
|
359
383
|
}.freeze
|
360
384
|
|
361
385
|
def initialize
|
@@ -370,8 +394,27 @@ module Fluent
|
|
370
394
|
@fields[name]
|
371
395
|
end
|
372
396
|
|
397
|
+
def load_schema(schema)
|
398
|
+
schema.each do |field|
|
399
|
+
raise ConfigError, 'field must have type' unless field.key?('type')
|
400
|
+
|
401
|
+
type = field['type'].downcase.to_sym
|
402
|
+
field_schema_class = FIELD_TYPES[type]
|
403
|
+
raise ConfigError, "Invalid field type: #{field['type']}" unless field_schema_class
|
404
|
+
|
405
|
+
field_schema = field_schema_class.new
|
406
|
+
@fields[field['name']] = field_schema
|
407
|
+
if type == :record
|
408
|
+
raise ConfigError, "record field must have fields" unless field.key?('fields')
|
409
|
+
field_schema.load_schema(field['fields'])
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
373
414
|
def register_field(name, type)
|
374
|
-
|
415
|
+
if @fields.key?(name) and @fields[name].type != :timestamp
|
416
|
+
raise ConfigError, "field #{name} is registered twice"
|
417
|
+
end
|
375
418
|
if name[/\./]
|
376
419
|
recordname = $`
|
377
420
|
fieldname = $'
|
data/test/helper.rb
CHANGED
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'google/api_client'
|
3
|
+
require 'fluent/plugin/buf_memory'
|
4
|
+
|
5
|
+
class BigQueryOutputTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
Fluent::Test.setup
|
8
|
+
end
|
9
|
+
|
10
|
+
CONFIG = %[
|
11
|
+
table foo
|
12
|
+
email foo@bar.example
|
13
|
+
private_key_path /path/to/key
|
14
|
+
project yourproject_id
|
15
|
+
dataset yourdataset_id
|
16
|
+
|
17
|
+
time_format %s
|
18
|
+
time_field time
|
19
|
+
|
20
|
+
field_integer time,status,bytes
|
21
|
+
field_string vhost,path,method,protocol,agent,referer,remote.host,remote.ip,remote.user
|
22
|
+
field_float requesttime
|
23
|
+
field_boolean bot_access,loginsession
|
24
|
+
]
|
25
|
+
|
26
|
+
API_SCOPE = "https://www.googleapis.com/auth/bigquery"
|
27
|
+
|
28
|
+
def create_driver(conf = CONFIG)
|
29
|
+
Fluent::Test::OutputTestDriver.new(Fluent::BigQueryOutput).configure(conf)
|
30
|
+
end
|
31
|
+
|
32
|
+
def stub_client(driver)
|
33
|
+
stub(client = Object.new) do |expect|
|
34
|
+
expect.discovered_api("bigquery", "v2") { stub! }
|
35
|
+
yield expect if defined?(yield)
|
36
|
+
end
|
37
|
+
stub(driver.instance).client { client }
|
38
|
+
client
|
39
|
+
end
|
40
|
+
|
41
|
+
def mock_client(driver)
|
42
|
+
mock(client = Object.new) do |expect|
|
43
|
+
yield expect
|
44
|
+
end
|
45
|
+
stub(driver.instance).client { client }
|
46
|
+
client
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_configure_table
|
50
|
+
driver = create_driver
|
51
|
+
assert_equal driver.instance.table, 'foo'
|
52
|
+
assert_nil driver.instance.tables
|
53
|
+
|
54
|
+
driver = create_driver(CONFIG.sub(/\btable\s+.*$/, 'tables foo,bar'))
|
55
|
+
assert_nil driver.instance.table
|
56
|
+
assert_equal driver.instance.tables, 'foo,bar'
|
57
|
+
|
58
|
+
assert_raise(Fluent::ConfigError, "'table' or 'tables' must be specified, and both are invalid") {
|
59
|
+
create_driver(CONFIG + "tables foo,bar")
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_configure_auth
|
64
|
+
key = stub!
|
65
|
+
mock(Google::APIClient::PKCS12).load_key('/path/to/key', 'notasecret') { key }
|
66
|
+
authorization = Object.new
|
67
|
+
asserter = mock!.authorize { authorization }
|
68
|
+
mock(Google::APIClient::JWTAsserter).new('foo@bar.example', API_SCOPE, key) { asserter }
|
69
|
+
|
70
|
+
mock.proxy(Google::APIClient).new.with_any_args {
|
71
|
+
mock!.__send__(:authorization=, authorization) {}
|
72
|
+
}
|
73
|
+
|
74
|
+
driver = create_driver(CONFIG)
|
75
|
+
driver.instance.client()
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_format_stream
|
79
|
+
now = Time.now
|
80
|
+
input = [
|
81
|
+
now,
|
82
|
+
{
|
83
|
+
"status" => "1",
|
84
|
+
"bytes" => 3.0,
|
85
|
+
"vhost" => :bar,
|
86
|
+
"path" => "/path/to/baz",
|
87
|
+
"method" => "GET",
|
88
|
+
"protocol" => "HTTP/0.9",
|
89
|
+
"agent" => "libwww",
|
90
|
+
"referer" => "http://referer.example",
|
91
|
+
"requesttime" => (now - 1).to_f.to_s,
|
92
|
+
"bot_access" => true,
|
93
|
+
"loginsession" => false,
|
94
|
+
"something-else" => "would be ignored",
|
95
|
+
"yet-another" => {
|
96
|
+
"foo" => "bar",
|
97
|
+
"baz" => 1,
|
98
|
+
},
|
99
|
+
"remote" => {
|
100
|
+
"host" => "remote.example",
|
101
|
+
"ip" => "192.0.2.1",
|
102
|
+
"port" => 12345,
|
103
|
+
"user" => "tagomoris",
|
104
|
+
}
|
105
|
+
}
|
106
|
+
]
|
107
|
+
expected = {
|
108
|
+
"json" => {
|
109
|
+
"time" => now.to_i,
|
110
|
+
"status" => 1,
|
111
|
+
"bytes" => 3,
|
112
|
+
"vhost" => "bar",
|
113
|
+
"path" => "/path/to/baz",
|
114
|
+
"method" => "GET",
|
115
|
+
"protocol" => "HTTP/0.9",
|
116
|
+
"agent" => "libwww",
|
117
|
+
"referer" => "http://referer.example",
|
118
|
+
"requesttime" => (now - 1).to_f.to_s.to_f,
|
119
|
+
"bot_access" => true,
|
120
|
+
"loginsession" => false,
|
121
|
+
"remote" => {
|
122
|
+
"host" => "remote.example",
|
123
|
+
"ip" => "192.0.2.1",
|
124
|
+
"user" => "tagomoris",
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
driver = create_driver(CONFIG)
|
130
|
+
mock_client(driver) do |expect|
|
131
|
+
expect.discovered_api("bigquery", "v2") { stub! }
|
132
|
+
end
|
133
|
+
driver.instance.start
|
134
|
+
buf = driver.instance.format_stream("my.tag", [input])
|
135
|
+
driver.instance.shutdown
|
136
|
+
|
137
|
+
assert_equal expected, MessagePack.unpack(buf)
|
138
|
+
end
|
139
|
+
|
140
|
+
[
|
141
|
+
# <time_format>, <time field type>, <time expectation generator>, <assertion>
|
142
|
+
[
|
143
|
+
"%s.%6N", "field_float",
|
144
|
+
lambda{|t| t.strftime("%s.%6N").to_f },
|
145
|
+
lambda{|recv, expected, actual|
|
146
|
+
recv.assert_in_delta(expected, actual, Float::EPSILON / 10**3)
|
147
|
+
}
|
148
|
+
],
|
149
|
+
[
|
150
|
+
"%Y-%m-%dT%H:%M:%SZ", "field_string",
|
151
|
+
lambda{|t| t.iso8601 },
|
152
|
+
:assert_equal.to_proc
|
153
|
+
],
|
154
|
+
[
|
155
|
+
"%a, %d %b %Y %H:%M:%S GMT", "field_string",
|
156
|
+
lambda{|t| t.httpdate },
|
157
|
+
:assert_equal.to_proc
|
158
|
+
],
|
159
|
+
].each do |format, type, expect_time, assert|
|
160
|
+
define_method("test_time_formats_#{format}") do
|
161
|
+
now = Time.now.utc
|
162
|
+
input = [ now, {} ]
|
163
|
+
expected = { "json" => { "time" => expect_time[now], } }
|
164
|
+
|
165
|
+
driver = create_driver(<<-CONFIG)
|
166
|
+
table foo
|
167
|
+
email foo@bar.example
|
168
|
+
private_key_path /path/to/key
|
169
|
+
project yourproject_id
|
170
|
+
dataset yourdataset_id
|
171
|
+
|
172
|
+
time_format #{format}
|
173
|
+
time_field time
|
174
|
+
#{type} time
|
175
|
+
CONFIG
|
176
|
+
stub_client(driver)
|
177
|
+
|
178
|
+
driver.instance.start
|
179
|
+
buf = driver.instance.format_stream("my.tag", [input])
|
180
|
+
driver.instance.shutdown
|
181
|
+
|
182
|
+
assert[self, expected["json"]["time"], MessagePack.unpack(buf)["json"]["time"]]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_format_nested_time
|
187
|
+
now = Time.now
|
188
|
+
input = [
|
189
|
+
now,
|
190
|
+
{
|
191
|
+
"metadata" => {
|
192
|
+
"node" => "mynode.example",
|
193
|
+
},
|
194
|
+
"log" => "something",
|
195
|
+
}
|
196
|
+
]
|
197
|
+
expected = {
|
198
|
+
"json" => {
|
199
|
+
"metadata" => {
|
200
|
+
"time" => now.strftime("%s").to_i,
|
201
|
+
"node" => "mynode.example",
|
202
|
+
},
|
203
|
+
"log" => "something",
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
driver = create_driver(<<-CONFIG)
|
208
|
+
table foo
|
209
|
+
email foo@bar.example
|
210
|
+
private_key_path /path/to/key
|
211
|
+
project yourproject_id
|
212
|
+
dataset yourdataset_id
|
213
|
+
|
214
|
+
time_format %s
|
215
|
+
time_field metadata.time
|
216
|
+
|
217
|
+
field_integer metadata.time
|
218
|
+
field_string metadata.node,log
|
219
|
+
CONFIG
|
220
|
+
stub_client(driver)
|
221
|
+
driver.instance.start
|
222
|
+
buf = driver.instance.format_stream("my.tag", [input])
|
223
|
+
driver.instance.shutdown
|
224
|
+
|
225
|
+
assert_equal expected, MessagePack.unpack(buf)
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_format_with_schema
|
229
|
+
now = Time.now
|
230
|
+
input = [
|
231
|
+
now,
|
232
|
+
{
|
233
|
+
"request" => {
|
234
|
+
"vhost" => :bar,
|
235
|
+
"path" => "/path/to/baz",
|
236
|
+
"method" => "GET",
|
237
|
+
"protocol" => "HTTP/0.9",
|
238
|
+
"agent" => "libwww",
|
239
|
+
"referer" => "http://referer.example",
|
240
|
+
"time" => (now - 1).to_f,
|
241
|
+
"bot_access" => true,
|
242
|
+
"loginsession" => false,
|
243
|
+
},
|
244
|
+
"response" => {
|
245
|
+
"status" => "1",
|
246
|
+
"bytes" => 3.0,
|
247
|
+
},
|
248
|
+
"remote" => {
|
249
|
+
"host" => "remote.example",
|
250
|
+
"ip" => "192.0.2.1",
|
251
|
+
"port" => 12345,
|
252
|
+
"user" => "tagomoris",
|
253
|
+
},
|
254
|
+
"something-else" => "would be ignored",
|
255
|
+
"yet-another" => {
|
256
|
+
"foo" => "bar",
|
257
|
+
"baz" => 1,
|
258
|
+
},
|
259
|
+
}
|
260
|
+
]
|
261
|
+
expected = {
|
262
|
+
"json" => {
|
263
|
+
"time" => now.to_i,
|
264
|
+
"request" => {
|
265
|
+
"vhost" => "bar",
|
266
|
+
"path" => "/path/to/baz",
|
267
|
+
"method" => "GET",
|
268
|
+
"protocol" => "HTTP/0.9",
|
269
|
+
"agent" => "libwww",
|
270
|
+
"referer" => "http://referer.example",
|
271
|
+
"time" => (now - 1).to_f,
|
272
|
+
"bot_access" => true,
|
273
|
+
"loginsession" => false,
|
274
|
+
},
|
275
|
+
"remote" => {
|
276
|
+
"host" => "remote.example",
|
277
|
+
"ip" => "192.0.2.1",
|
278
|
+
"user" => "tagomoris",
|
279
|
+
},
|
280
|
+
"response" => {
|
281
|
+
"status" => 1,
|
282
|
+
"bytes" => 3,
|
283
|
+
},
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
driver = create_driver(<<-CONFIG)
|
288
|
+
table foo
|
289
|
+
email foo@bar.example
|
290
|
+
private_key_path /path/to/key
|
291
|
+
project yourproject_id
|
292
|
+
dataset yourdataset_id
|
293
|
+
|
294
|
+
time_format %s
|
295
|
+
time_field time
|
296
|
+
|
297
|
+
schema_path #{File.join(File.dirname(__FILE__), "testdata", "apache.schema")}
|
298
|
+
field_integer time
|
299
|
+
CONFIG
|
300
|
+
mock_client(driver) do |expect|
|
301
|
+
expect.discovered_api("bigquery", "v2") { stub! }
|
302
|
+
end
|
303
|
+
driver.instance.start
|
304
|
+
buf = driver.instance.format_stream("my.tag", [input])
|
305
|
+
driver.instance.shutdown
|
306
|
+
|
307
|
+
assert_equal expected, MessagePack.unpack(buf)
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_write
|
311
|
+
entry = {"json" => {"a" => "b"}}, {"json" => {"b" => "c"}}
|
312
|
+
driver = create_driver(CONFIG)
|
313
|
+
mock_client(driver) do |expect|
|
314
|
+
expect.discovered_api("bigquery", "v2") { mock!.tabledata.mock!.insert_all { Object.new } }
|
315
|
+
expect.execute(
|
316
|
+
:api_method => anything,
|
317
|
+
:parameters => {
|
318
|
+
'projectId' => 'yourproject_id',
|
319
|
+
'datasetId' => 'yourdataset_id',
|
320
|
+
'tableId' => 'foo',
|
321
|
+
},
|
322
|
+
:body_object => {
|
323
|
+
'rows' => [entry]
|
324
|
+
}
|
325
|
+
) { stub!.success? { true } }
|
326
|
+
end
|
327
|
+
|
328
|
+
chunk = Fluent::MemoryBufferChunk.new("my.tag")
|
329
|
+
chunk << entry.to_msgpack
|
330
|
+
|
331
|
+
driver.instance.start
|
332
|
+
driver.instance.write(chunk)
|
333
|
+
driver.instance.shutdown
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"name": "time",
|
4
|
+
"type": "TIMESTAMP",
|
5
|
+
"mode": "REQUIRED"
|
6
|
+
},
|
7
|
+
{
|
8
|
+
"name": "request",
|
9
|
+
"type": "RECORD",
|
10
|
+
"mode": "REQUIRED",
|
11
|
+
"fields": [
|
12
|
+
{
|
13
|
+
"name": "vhost",
|
14
|
+
"type": "STRING",
|
15
|
+
"mode": "NULLABLE"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"name": "path",
|
19
|
+
"type": "STRING",
|
20
|
+
"mode": "REQUIRED"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"name": "method",
|
24
|
+
"type": "STRING",
|
25
|
+
"mode": "REQUIRED"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"name": "protocol",
|
29
|
+
"type": "STRING",
|
30
|
+
"mode": "REQUIRED"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "agent",
|
34
|
+
"type": "STRING",
|
35
|
+
"mode": "REQUIRED"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"name": "referer",
|
39
|
+
"type": "STRING",
|
40
|
+
"mode": "NULLABLE"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"name": "time",
|
44
|
+
"type": "TIMESTAMP",
|
45
|
+
"mode": "NULLABLE"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"name": "bot_access",
|
49
|
+
"type": "BOOLEAN",
|
50
|
+
"mode": "NULLABLE"
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"name": "loginsession",
|
54
|
+
"type": "BOOLEAN",
|
55
|
+
"mode": "NULLABLE"
|
56
|
+
}
|
57
|
+
]
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"name": "remote",
|
61
|
+
"type": "RECORD",
|
62
|
+
"mode": "NULLABLE",
|
63
|
+
"fields": [
|
64
|
+
{
|
65
|
+
"name": "host",
|
66
|
+
"type": "STRING",
|
67
|
+
"mode": "NULLABLE"
|
68
|
+
},
|
69
|
+
{
|
70
|
+
"name": "ip",
|
71
|
+
"type": "STRING",
|
72
|
+
"mode": "REQUIRED"
|
73
|
+
},
|
74
|
+
{
|
75
|
+
"name": "user",
|
76
|
+
"type": "STRING",
|
77
|
+
"mode": "NULLABLE"
|
78
|
+
}
|
79
|
+
]
|
80
|
+
},
|
81
|
+
{
|
82
|
+
"name": "response",
|
83
|
+
"type": "RECORD",
|
84
|
+
"mode": "REQUIRED",
|
85
|
+
"fields": [
|
86
|
+
{
|
87
|
+
"name": "status",
|
88
|
+
"type": "INTEGER",
|
89
|
+
"mode": "REQUIRED"
|
90
|
+
},
|
91
|
+
{
|
92
|
+
"name": "bytes",
|
93
|
+
"type": "INTEGER",
|
94
|
+
"mode": "REQUIRED"
|
95
|
+
}
|
96
|
+
]
|
97
|
+
}
|
98
|
+
]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-bigquery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TAGOMORI Satoshi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rr
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: google-api-client
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,6 +131,7 @@ extensions: []
|
|
117
131
|
extra_rdoc_files: []
|
118
132
|
files:
|
119
133
|
- .gitignore
|
134
|
+
- .travis.yml
|
120
135
|
- Gemfile
|
121
136
|
- LICENSE.txt
|
122
137
|
- README.md
|
@@ -126,6 +141,8 @@ files:
|
|
126
141
|
- lib/fluent/plugin/bigquery/version.rb
|
127
142
|
- lib/fluent/plugin/out_bigquery.rb
|
128
143
|
- test/helper.rb
|
144
|
+
- test/plugin/test_out_bigquery.rb
|
145
|
+
- test/plugin/testdata/apache.schema
|
129
146
|
- test/test_load_request_body_wrapper.rb
|
130
147
|
homepage: https://github.com/tagomoris/fluent-plugin-bigquery
|
131
148
|
licenses:
|
@@ -153,5 +170,7 @@ specification_version: 4
|
|
153
170
|
summary: Fluentd plugin to store data on Google BigQuery
|
154
171
|
test_files:
|
155
172
|
- test/helper.rb
|
173
|
+
- test/plugin/test_out_bigquery.rb
|
174
|
+
- test/plugin/testdata/apache.schema
|
156
175
|
- test/test_load_request_body_wrapper.rb
|
157
176
|
has_rdoc:
|