Class: Fluent::BigQueryOutput

Inherits:
BufferedOutput
  • Object
show all
Defined in:
lib/fluent/plugin/out_bigquery.rb

Overview

TODO: error classes for each api error responses class BigQueryAPIError < StandardError end

Defined Under Namespace

Classes: BooleanFieldSchema, FieldSchema, FloatFieldSchema, IntegerFieldSchema, RecordSchema, StringFieldSchema

Instance Method Summary collapse

Constructor Details

#initializeBigQueryOutput

Table types https://developers.google.com/bigquery/docs/tables

type - The following data types are supported; see Data Formats for details on each data type: STRING INTEGER FLOAT BOOLEAN RECORD A JSON object, used when importing nested records. This type is only available when using JSON source files.

mode - Whether a field can be null. The following values are supported: NULLABLE - The cell can be null. REQUIRED - The cell cannot be null. REPEATED - Zero or more repeated simple or nested subfields. This mode is only supported when using JSON source files.



118
119
120
121
122
123
124
# File 'lib/fluent/plugin/out_bigquery.rb', line 118

def initialize
  super
  require 'google/api_client'
  require 'google/api_client/client_secrets'
  require 'google/api_client/auth/installed_app'
  require 'google/api_client/auth/compute_service_account'
end

Instance Method Details

#clientObject



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/fluent/plugin/out_bigquery.rb', line 192

def client
  return @cached_client if @cached_client && @cached_client_expiration > Time.now

  client = Google::APIClient.new(
    :application_name => 'Fluentd BigQuery plugin',
    :application_version => Fluent::BigQueryPlugin::VERSION
  )

  case @auth_method
  when 'private_key'
    key = Google::APIClient::PKCS12.load_key( @private_key_path, @private_key_passphrase )
    asserter = Google::APIClient::JWTAsserter.new(
      @email,
      "https://www.googleapis.com/auth/bigquery",
      key
    )
    # refresh_auth
    client.authorization = asserter.authorize

  when 'compute_engine'
    auth = Google::APIClient::ComputeServiceAccount.new
    auth.fetch_access_token!
    client.authorization = auth

  else
    raise ConfigError, "Unknown auth method: #{@auth_method}"
  end

  @cached_client_expiration = Time.now + 1800
  @cached_client = client
end

#configure(conf) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/fluent/plugin/out_bigquery.rb', line 126

def configure(conf)
  super

  case @auth_method
  when 'private_key'
    if !@email || !@private_key_path
      raise Fluent::ConfigError, "'email' and 'private_key_path' must be specified if auth_method == 'private_key'"
    end
  when 'compute_engine'
    # Do nothing
  else
    raise Fluent::ConfigError, "unrecognized 'auth_method': #{@auth_method}"
  end

  if (!@table && !@tables) || (@table && @table)
    raise Fluent::ConfigError, "'table' or 'tables' must be specified, and both are invalid"
  end

  @tablelist = @tables ? @tables.split(',') : [@table]

  @fields = RecordSchema.new
  if @field_string
    @field_string.split(',').each do |fieldname|
      @fields.register_field fieldname, :string
    end
  end
  if @field_integer
    @field_integer.split(',').each do |fieldname|
      @fields.register_field fieldname, :integer
    end
  end
  if @field_float
    @field_float.split(',').each do |fieldname|
      @fields.register_field fieldname, :float
    end
  end
  if @field_boolean
    @field_boolean.split(',').each do |fieldname|
      @fields.register_field fieldname, :boolean
    end
  end

  if @localtime.nil?
    if @utc
      @localtime = false
    end
  end
  @timef = TimeFormatter.new(@time_format, @localtime)
end

#format_stream(tag, es) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/fluent/plugin/out_bigquery.rb', line 259

def format_stream(tag, es)
  super
  buf = ''
  es.each do |time, record|
    row = if @time_field
            @fields.format(record.merge({@time_field => @timef.format(time)}))
          else
            @fields.format(record)
          end
    buf << {"json" => row}.to_msgpack unless row.empty?
  end
  buf
end

#insert(table_id, rows) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/fluent/plugin/out_bigquery.rb', line 224

def insert(table_id, rows)
  res = client().execute(
    :api_method => @bq.tabledata.insert_all,
    :parameters => {
      'projectId' => @project,
      'datasetId' => @dataset,
      'tableId' => table_id,
    },
    :body_object => {
      "rows" => rows
    }
  )
  unless res.success?
    # api_error? -> client cache clear
    @cached_client = nil

    message = res.body
    if res.body =~ /^\{/
      begin
        res_obj = JSON.parse(res.body)
        message = res_obj['error']['message'] || res.body
      rescue => e
        $log.warn "Parse error: google api error response body", :body => res.body
      end
    end
    $log.error "tabledata.insertAll API", :project_id => @project_id, :dataset => @dataset_id, :table => table_id, :code => res.status, :message => message
    raise "failed to insert into bigquery" # TODO: error class
  end
end

#loadObject

Raises:

  • (NotImplementedError)


254
255
256
257
# File 'lib/fluent/plugin/out_bigquery.rb', line 254

def load
  # https://developers.google.com/bigquery/loading-data-into-bigquery#loaddatapostrequest
  raise NotImplementedError # TODO
end

#shutdownObject



187
188
189
190
# File 'lib/fluent/plugin/out_bigquery.rb', line 187

def shutdown
  super
  # nothing to do
end

#startObject



176
177
178
179
180
181
182
183
184
185
# File 'lib/fluent/plugin/out_bigquery.rb', line 176

def start
  super

  @bq = client.discovered_api("bigquery", "v2") # TODO: refresh with specified expiration
  @cached_client = nil
  @cached_client_expiration = nil

  @tables_queue = @tablelist.dup.shuffle
  @tables_mutex = Mutex.new
end

#write(chunk) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/fluent/plugin/out_bigquery.rb', line 273

def write(chunk)
  rows = []
  chunk.msgpack_each do |row_object|
    # TODO: row size limit
    rows << row_object
  end

  # TODO: method

  insert_table = @tables_mutex.synchronize do
    t = @tables_queue.shift
    @tables_queue.push t
    t
  end
  insert(insert_table, rows)
end