fluent-plugin-google-cloud 0.5.2 → 0.5.3.grpc.alpha.1
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/Gemfile.lock +9 -1
- data/Rakefile +17 -7
- data/fluent-plugin-google-cloud.gemspec +3 -1
- data/lib/fluent/plugin/out_google_cloud.rb +640 -79
- data/lib/google/logging/type/http_request.rb +30 -0
- data/lib/google/logging/type/log_severity.rb +26 -0
- data/lib/google/logging/v1/log_entry.rb +52 -0
- data/lib/google/logging/v1/logging.rb +84 -0
- data/lib/google/logging/v1/logging_services.rb +150 -0
- data/test/plugin/test_out_google_cloud.rb +14 -0
- metadata +37 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4289702253d986d7711b6e0a97712b13702901c3
         | 
| 4 | 
            +
              data.tar.gz: aac7a196ff97e4c70234772ad5755861d80549aa
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4eb63be9e1eb8492ff627270ba13ab5b2d1cbe4dc1c01a0e2b7b14cc3192084d3d477ee9338c25c3af3ab8875e2203ee8e0d7fac2ddbda52fbbc6c5983959a16
         | 
| 7 | 
            +
              data.tar.gz: 8fd692a696fec2163b7c1935cf68e488d3eb4d780ebe90bd5e22f53d4e06cab5d2dbc8dedca419a8218019ce13eacf9d15eebe76a71af40ad02514dec80887dc
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,10 +1,12 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                fluent-plugin-google-cloud (0.5. | 
| 4 | 
            +
                fluent-plugin-google-cloud (0.5.3.grpc.alpha.1)
         | 
| 5 5 | 
             
                  fluentd (~> 0.10, <= 0.13)
         | 
| 6 6 | 
             
                  google-api-client (> 0.9)
         | 
| 7 | 
            +
                  googleapis-common-protos (~> 1.1)
         | 
| 7 8 | 
             
                  googleauth (~> 0.4)
         | 
| 9 | 
            +
                  grpc (~> 0.15)
         | 
| 8 10 | 
             
                  json (~> 1.8)
         | 
| 9 11 |  | 
| 10 12 | 
             
            GEM
         | 
| @@ -39,6 +41,9 @@ GEM | |
| 39 41 | 
             
                  representable (~> 2.3.0)
         | 
| 40 42 | 
             
                  retriable (~> 2.0)
         | 
| 41 43 | 
             
                  thor (~> 0.19)
         | 
| 44 | 
            +
                google-protobuf (3.0.0.alpha.5.0.5.1)
         | 
| 45 | 
            +
                googleapis-common-protos (1.1.1)
         | 
| 46 | 
            +
                  google-protobuf (~> 3.0.0.alpha.5.0)
         | 
| 42 47 | 
             
                googleauth (0.5.1)
         | 
| 43 48 | 
             
                  faraday (~> 0.9)
         | 
| 44 49 | 
             
                  jwt (~> 1.4)
         | 
| @@ -47,6 +52,9 @@ GEM | |
| 47 52 | 
             
                  multi_json (~> 1.11)
         | 
| 48 53 | 
             
                  os (~> 0.9)
         | 
| 49 54 | 
             
                  signet (~> 0.7)
         | 
| 55 | 
            +
                grpc (0.15.0)
         | 
| 56 | 
            +
                  google-protobuf (~> 3.0.0.alpha.5.0.3)
         | 
| 57 | 
            +
                  googleauth (~> 0.5.1)
         | 
| 50 58 | 
             
                hashdiff (0.3.0)
         | 
| 51 59 | 
             
                http_parser.rb (0.6.0)
         | 
| 52 60 | 
             
                httpclient (2.8.0)
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -18,15 +18,25 @@ end | |
| 18 18 |  | 
| 19 19 | 
             
            # Building the gem will use the local file mode, so ensure it's world-readable.
         | 
| 20 20 | 
             
            # https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud/issues/53
         | 
| 21 | 
            -
            desc ' | 
| 22 | 
            -
            task : | 
| 23 | 
            -
               | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 21 | 
            +
            desc 'Fix file permissions'
         | 
| 22 | 
            +
            task :fix_perms do
         | 
| 23 | 
            +
              files = [
         | 
| 24 | 
            +
                'lib/fluent/plugin/out_google_cloud.rb',
         | 
| 25 | 
            +
                'lib/google/**/*.rb'
         | 
| 26 | 
            +
              ].flat_map do |file|
         | 
| 27 | 
            +
                file.include?('*') ? Dir.glob(file) : [file]
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              files.each do |file|
         | 
| 31 | 
            +
                mode = File.stat(file).mode & 0777
         | 
| 32 | 
            +
                next unless mode & 0444 != 0444
         | 
| 33 | 
            +
                puts "Changing mode of #{file} from #{mode.to_s(8)} to "\
         | 
| 34 | 
            +
                     "#{(mode | 0444).to_s(8)}"
         | 
| 35 | 
            +
                chmod mode | 0444, file
         | 
| 36 | 
            +
              end
         | 
| 27 37 | 
             
            end
         | 
| 28 38 |  | 
| 29 39 | 
             
            desc 'Run unit tests and RuboCop to check for style violations'
         | 
| 30 | 
            -
            task all: [:test, :rubocop, : | 
| 40 | 
            +
            task all: [:test, :rubocop, :fix_perms]
         | 
| 31 41 |  | 
| 32 42 | 
             
            task default: :all
         | 
| @@ -10,7 +10,7 @@ eos | |
| 10 10 | 
             
              gem.homepage      = \
         | 
| 11 11 | 
             
                'https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud'
         | 
| 12 12 | 
             
              gem.license       = 'Apache-2.0'
         | 
| 13 | 
            -
              gem.version       = '0.5. | 
| 13 | 
            +
              gem.version       = '0.5.3.grpc.alpha.1'
         | 
| 14 14 | 
             
              gem.authors       = ['Todd Derr', 'Alex Robinson']
         | 
| 15 15 | 
             
              gem.email         = ['salty@google.com']
         | 
| 16 16 | 
             
              gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
         | 
| @@ -20,8 +20,10 @@ eos | |
| 20 20 | 
             
              gem.require_paths = ['lib']
         | 
| 21 21 |  | 
| 22 22 | 
             
              gem.add_runtime_dependency 'fluentd', '~> 0.10', '<= 0.13'
         | 
| 23 | 
            +
              gem.add_runtime_dependency 'googleapis-common-protos', '~> 1.1'
         | 
| 23 24 | 
             
              gem.add_runtime_dependency 'google-api-client', '> 0.9'
         | 
| 24 25 | 
             
              gem.add_runtime_dependency 'googleauth', '~> 0.4'
         | 
| 26 | 
            +
              gem.add_runtime_dependency 'grpc', '~> 0.15'
         | 
| 25 27 | 
             
              gem.add_runtime_dependency 'json', '~> 1.8'
         | 
| 26 28 |  | 
| 27 29 | 
             
              gem.add_development_dependency 'mocha', '~> 1.1'
         | 
| @@ -11,6 +11,7 @@ | |
| 11 11 | 
             
            # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         | 
| 12 12 | 
             
            # See the License for the specific language governing permissions and
         | 
| 13 13 | 
             
            # limitations under the License.
         | 
| 14 | 
            +
            require 'grpc'
         | 
| 14 15 | 
             
            require 'json'
         | 
| 15 16 | 
             
            require 'open-uri'
         | 
| 16 17 | 
             
            require 'socket'
         | 
| @@ -18,6 +19,9 @@ require 'time' | |
| 18 19 | 
             
            require 'yaml'
         | 
| 19 20 | 
             
            require 'google/apis'
         | 
| 20 21 | 
             
            require 'google/apis/logging_v1beta3'
         | 
| 22 | 
            +
            require 'google/logging/v1/logging'
         | 
| 23 | 
            +
            require 'google/logging/v1/logging_services'
         | 
| 24 | 
            +
            require 'google/logging/v1/log_entry'
         | 
| 21 25 | 
             
            require 'googleauth'
         | 
| 22 26 |  | 
| 23 27 | 
             
            module Fluent
         | 
| @@ -26,7 +30,7 @@ module Fluent | |
| 26 30 | 
             
                Fluent::Plugin.register_output('google_cloud', self)
         | 
| 27 31 |  | 
| 28 32 | 
             
                PLUGIN_NAME = 'Fluentd Google Cloud Logging plugin'
         | 
| 29 | 
            -
                PLUGIN_VERSION = '0.5. | 
| 33 | 
            +
                PLUGIN_VERSION = '0.5.3.grpc.alpha.1'
         | 
| 30 34 |  | 
| 31 35 | 
             
                # Constants for service names.
         | 
| 32 36 | 
             
                APPENGINE_SERVICE = 'appengine.googleapis.com'
         | 
| @@ -114,6 +118,10 @@ module Fluent | |
| 114 118 | 
             
                #   }
         | 
| 115 119 | 
             
                config_param :labels, :hash, :default => nil
         | 
| 116 120 |  | 
| 121 | 
            +
                # Whether to use gRPC instead of REST/JSON to communicate to the
         | 
| 122 | 
            +
                # Cloud Logging API.
         | 
| 123 | 
            +
                config_param :use_grpc, :bool, :default => false
         | 
| 124 | 
            +
             | 
| 117 125 | 
             
                # DEPRECATED: The following parameters, if present in the config
         | 
| 118 126 | 
             
                # indicate that the plugin configuration must be updated.
         | 
| 119 127 | 
             
                config_param :auth_method, :string, :default => nil
         | 
| @@ -348,23 +356,34 @@ module Fluent | |
| 348 356 | 
             
                        end
         | 
| 349 357 | 
             
                      end
         | 
| 350 358 | 
             
                    end
         | 
| 359 | 
            +
             | 
| 351 360 | 
             
                    arr.each do |time, record|
         | 
| 352 361 | 
             
                      next unless record.is_a?(Hash)
         | 
| 353 362 |  | 
| 354 | 
            -
                       | 
| 355 | 
            -
                         | 
| 356 | 
            -
                           | 
| 357 | 
            -
             | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 360 | 
            -
             | 
| 363 | 
            +
                      if @use_grpc
         | 
| 364 | 
            +
                        entry = Google::Logging::V1::LogEntry.new(
         | 
| 365 | 
            +
                          metadata: Google::Logging::V1::LogEntryMetadata.new(
         | 
| 366 | 
            +
                            service_name: @service_name.encode('utf-8'),
         | 
| 367 | 
            +
                            project_id: @project_id.encode('utf-8'),
         | 
| 368 | 
            +
                            zone: @zone.encode('utf-8'),
         | 
| 369 | 
            +
                            labels: {}
         | 
| 370 | 
            +
                          ))
         | 
| 371 | 
            +
                      else
         | 
| 372 | 
            +
                        entry = Google::Apis::LoggingV1beta3::LogEntry.new(
         | 
| 373 | 
            +
                          metadata: Google::Apis::LoggingV1beta3::LogEntryMetadata.new(
         | 
| 374 | 
            +
                            service_name: @service_name,
         | 
| 375 | 
            +
                            project_id: @project_id,
         | 
| 376 | 
            +
                            zone: @zone,
         | 
| 377 | 
            +
                            labels: {}
         | 
| 378 | 
            +
                          ))
         | 
| 379 | 
            +
                      end
         | 
| 361 380 |  | 
| 362 381 | 
             
                      if @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
         | 
| 363 382 | 
             
                        @cloudfunctions_log_match =
         | 
| 364 383 | 
             
                          @cloudfunctions_log_regexp.match(record['log'])
         | 
| 365 384 | 
             
                      end
         | 
| 366 385 | 
             
                      if @service_name == CONTAINER_SERVICE
         | 
| 367 | 
            -
                        # Move the stdout/stderr annotation from the record into a label
         | 
| 386 | 
            +
                        # Move the stdout/stderr annotation from the record into a label.
         | 
| 368 387 | 
             
                        field_to_label(record, 'stream', entry.metadata.labels,
         | 
| 369 388 | 
             
                                       "#{CONTAINER_SERVICE}/stream")
         | 
| 370 389 | 
             
                        # If the record has been annotated by the kubernetes_metadata_filter
         | 
| @@ -397,9 +416,28 @@ module Fluent | |
| 397 416 | 
             
                        end
         | 
| 398 417 | 
             
                      end
         | 
| 399 418 |  | 
| 400 | 
            -
                       | 
| 401 | 
            -
                       | 
| 402 | 
            -
             | 
| 419 | 
            +
                      ts_secs, ts_nanos = compute_timestamp(record, time)
         | 
| 420 | 
            +
                      if @use_grpc
         | 
| 421 | 
            +
                        entry.metadata.timestamp = Google::Protobuf::Timestamp.new(
         | 
| 422 | 
            +
                          seconds: ts_secs,
         | 
| 423 | 
            +
                          nanos: ts_nanos
         | 
| 424 | 
            +
                        )
         | 
| 425 | 
            +
             | 
| 426 | 
            +
                        entry.metadata.severity =
         | 
| 427 | 
            +
                          grpc_severity(compute_severity(record, entry))
         | 
| 428 | 
            +
             | 
| 429 | 
            +
                        set_http_request_grpc(record, entry) # FIXME
         | 
| 430 | 
            +
                      else
         | 
| 431 | 
            +
                        entry.metadata.timestamp = {
         | 
| 432 | 
            +
                          seconds: ts_secs,
         | 
| 433 | 
            +
                          nanos: ts_nanos
         | 
| 434 | 
            +
                        }
         | 
| 435 | 
            +
             | 
| 436 | 
            +
                        entry.metadata.severity =
         | 
| 437 | 
            +
                          compute_severity(record, entry)
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                        set_http_request(record, entry)
         | 
| 440 | 
            +
                      end
         | 
| 403 441 |  | 
| 404 442 | 
             
                      # If a field is present in the label_map, send its value as a label
         | 
| 405 443 | 
             
                      # (mapping the field name to label name as specified in the config)
         | 
| @@ -417,8 +455,12 @@ module Fluent | |
| 417 455 | 
             
                          @cloudfunctions_log_match['execution_id']
         | 
| 418 456 | 
             
                      end
         | 
| 419 457 |  | 
| 420 | 
            -
                       | 
| 421 | 
            -
             | 
| 458 | 
            +
                      if @use_grpc
         | 
| 459 | 
            +
                        set_payload_grpc(record, entry, is_json)
         | 
| 460 | 
            +
                      else
         | 
| 461 | 
            +
                        set_payload(record, entry, is_json)
         | 
| 462 | 
            +
                        entry.metadata.labels = nil if entry.metadata.labels.empty?
         | 
| 463 | 
            +
                      end
         | 
| 422 464 |  | 
| 423 465 | 
             
                      entries.push(entry)
         | 
| 424 466 | 
             
                    end
         | 
| @@ -427,52 +469,449 @@ module Fluent | |
| 427 469 |  | 
| 428 470 | 
             
                    log_name = log_name(tag, labels)
         | 
| 429 471 |  | 
| 430 | 
            -
                     | 
| 431 | 
            -
                       | 
| 432 | 
            -
             | 
| 433 | 
            -
             | 
| 434 | 
            -
             | 
| 435 | 
            -
             | 
| 436 | 
            -
             | 
| 437 | 
            -
             | 
| 438 | 
            -
             | 
| 439 | 
            -
             | 
| 440 | 
            -
             | 
| 441 | 
            -
             | 
| 442 | 
            -
                           | 
| 443 | 
            -
             | 
| 444 | 
            -
             | 
| 445 | 
            -
             | 
| 446 | 
            -
             | 
| 447 | 
            -
             | 
| 448 | 
            -
             | 
| 449 | 
            -
             | 
| 450 | 
            -
                         | 
| 451 | 
            -
                        @ | 
| 472 | 
            +
                    if @use_grpc
         | 
| 473 | 
            +
                      begin
         | 
| 474 | 
            +
                        # Does the actual write to the cloud logging api.
         | 
| 475 | 
            +
             | 
| 476 | 
            +
                        client = api_client
         | 
| 477 | 
            +
             | 
| 478 | 
            +
                        labels_utf8_pairs = labels.map do |k, v|
         | 
| 479 | 
            +
                          [k.encode('utf-8'), v.encode('utf-8')]
         | 
| 480 | 
            +
                        end
         | 
| 481 | 
            +
                        utf8_log_name = log_name.encode('utf-8')
         | 
| 482 | 
            +
             | 
| 483 | 
            +
                        write_request = Google::Logging::V1::WriteLogEntriesRequest.new(
         | 
| 484 | 
            +
                          log_name: "projects/#{@project_id}/logs/#{utf8_log_name}",
         | 
| 485 | 
            +
                          common_labels: Hash[labels_utf8_pairs],
         | 
| 486 | 
            +
                          entries: entries
         | 
| 487 | 
            +
                        )
         | 
| 488 | 
            +
             | 
| 489 | 
            +
                        client.write_log_entries(write_request)
         | 
| 490 | 
            +
             | 
| 491 | 
            +
                        # Let the user explicitly know when the first call succeeded,
         | 
| 492 | 
            +
                        # to aid with verification and troubleshooting.
         | 
| 493 | 
            +
                        unless @successful_call
         | 
| 494 | 
            +
                          @successful_call = true
         | 
| 495 | 
            +
                          @log.info 'Successfully sent gRPC to Google Cloud Logging API.'
         | 
| 496 | 
            +
                        end
         | 
| 497 | 
            +
             | 
| 498 | 
            +
                      rescue GRPC::Cancelled => error
         | 
| 499 | 
            +
                        # RPC cancelled, so retry via re-raising the error.
         | 
| 500 | 
            +
                        raise error
         | 
| 501 | 
            +
             | 
| 502 | 
            +
                      rescue GRPC::BadStatus => error
         | 
| 503 | 
            +
                        case error.code
         | 
| 504 | 
            +
                        when GRPC::Core::StatusCodes::CANCELLED,
         | 
| 505 | 
            +
                             GRPC::Core::StatusCodes::UNAVAILABLE,
         | 
| 506 | 
            +
                             GRPC::Core::StatusCodes::DEADLINE_EXCEEDED,
         | 
| 507 | 
            +
                             GRPC::Core::StatusCodes::INTERNAL,
         | 
| 508 | 
            +
                             GRPC::Core::StatusCodes::UNKNOWN
         | 
| 509 | 
            +
                          # TODO
         | 
| 510 | 
            +
                          # Server error, so retry via re-raising the error.
         | 
| 511 | 
            +
                          raise error
         | 
| 512 | 
            +
                        when GRPC::Core::StatusCodes::UNIMPLEMENTED,
         | 
| 513 | 
            +
                             GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED
         | 
| 514 | 
            +
                          # Most client errors indicate a problem with the request itself
         | 
| 515 | 
            +
                          # and should not be retried.
         | 
| 516 | 
            +
                          dropped = entries.length
         | 
| 517 | 
            +
                          @log.warn "Dropping #{dropped} log message(s)",
         | 
| 518 | 
            +
                                    error: error.to_s, error_code: error.code.to_s
         | 
| 519 | 
            +
                        when GRPC::Core::StatusCodes::UNAUTHENTICATED
         | 
| 520 | 
            +
                          # Authorization error.
         | 
| 521 | 
            +
                          # These are usually solved via a `gcloud auth` call, or by
         | 
| 522 | 
            +
                          # modifying the permissions on the Google Cloud project.
         | 
| 523 | 
            +
                          dropped = entries.length
         | 
| 524 | 
            +
                          @log.warn "Dropping #{dropped} log message(s)",
         | 
| 525 | 
            +
                                    error: error.to_s, error_code: error.code.to_s
         | 
| 526 | 
            +
                        else
         | 
| 527 | 
            +
                          # Assume this is a problem with the request itself
         | 
| 528 | 
            +
                          # and don't retry.
         | 
| 529 | 
            +
                          dropped = entries.length
         | 
| 530 | 
            +
                          @log.error "Unknown response code #{error.code} from the "\
         | 
| 531 | 
            +
                                     "server, dropping #{dropped} log message(s)",
         | 
| 532 | 
            +
                                     error: error.to_s, error_code: error.code.to_s
         | 
| 533 | 
            +
                        end
         | 
| 452 534 | 
             
                      end
         | 
| 535 | 
            +
                    else
         | 
| 536 | 
            +
                      begin
         | 
| 537 | 
            +
                        # Does the actual write to the cloud logging api.
         | 
| 538 | 
            +
             | 
| 539 | 
            +
                        client = api_client
         | 
| 540 | 
            +
             | 
| 541 | 
            +
                        # The URI of the write is constructed by the Google::Api request;
         | 
| 542 | 
            +
                        # it is equivalent to this URL:
         | 
| 543 | 
            +
                        # 'https://logging.googleapis.com/v1beta3/projects/' \
         | 
| 544 | 
            +
                        #   "#{@project_id}/logs/#{log_name}/entries:write"
         | 
| 545 | 
            +
                        write_request = \
         | 
| 546 | 
            +
                          Google::Apis::LoggingV1beta3::WriteLogEntriesRequest.new(
         | 
| 547 | 
            +
                            common_labels: labels,
         | 
| 548 | 
            +
                            entries: entries)
         | 
| 549 | 
            +
             | 
| 550 | 
            +
                        # TODO: RequestOptions
         | 
| 551 | 
            +
                        client.write_log_entries(@project_id, log_name, write_request)
         | 
| 552 | 
            +
             | 
| 553 | 
            +
                        # Let the user explicitly know when the first call succeeded,
         | 
| 554 | 
            +
                        # to aid with verification and troubleshooting.
         | 
| 555 | 
            +
                        unless @successful_call
         | 
| 556 | 
            +
                          @successful_call = true
         | 
| 557 | 
            +
                          @log.info 'Successfully sent to Google Cloud Logging API.'
         | 
| 558 | 
            +
                        end
         | 
| 453 559 |  | 
| 454 | 
            -
             | 
| 455 | 
            -
             | 
| 456 | 
            -
             | 
| 457 | 
            -
             | 
| 458 | 
            -
             | 
| 459 | 
            -
             | 
| 460 | 
            -
             | 
| 461 | 
            -
             | 
| 462 | 
            -
             | 
| 463 | 
            -
             | 
| 464 | 
            -
             | 
| 465 | 
            -
             | 
| 466 | 
            -
             | 
| 467 | 
            -
             | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 | 
            -
             | 
| 560 | 
            +
                      rescue Google::Apis::ServerError => error
         | 
| 561 | 
            +
                        # Server error, so retry via re-raising the error.
         | 
| 562 | 
            +
                        raise error
         | 
| 563 | 
            +
             | 
| 564 | 
            +
                      rescue Google::Apis::AuthorizationError => error
         | 
| 565 | 
            +
                        # Authorization error.
         | 
| 566 | 
            +
                        # These are usually solved via a `gcloud auth` call, or by modifying
         | 
| 567 | 
            +
                        # the permissions on the Google Cloud project.
         | 
| 568 | 
            +
                        dropped = entries.length
         | 
| 569 | 
            +
                        @log.warn "Dropping #{dropped} log message(s)",
         | 
| 570 | 
            +
                                  error_class: error.class.to_s, error: error.to_s
         | 
| 571 | 
            +
             | 
| 572 | 
            +
                      rescue Google::Apis::ClientError => error
         | 
| 573 | 
            +
                        # Most ClientErrors indicate a problem with the request itself and
         | 
| 574 | 
            +
                        # should not be retried.
         | 
| 575 | 
            +
                        dropped = entries.length
         | 
| 576 | 
            +
                        @log.warn "Dropping #{dropped} log message(s)",
         | 
| 577 | 
            +
                                  error_class: error.class.to_s, error: error.to_s
         | 
| 578 | 
            +
                      end
         | 
| 472 579 | 
             
                    end
         | 
| 473 580 | 
             
                  end
         | 
| 474 581 | 
             
                end
         | 
| 475 582 |  | 
| 583 | 
            +
                # def old_write(chunk)
         | 
| 584 | 
            +
                #   # Group the entries since we have to make one call per tag.
         | 
| 585 | 
            +
                #   grouped_entries = {}
         | 
| 586 | 
            +
                #   chunk.msgpack_each do |tag, *arr|
         | 
| 587 | 
            +
                #     grouped_entries[tag] = [] unless grouped_entries.key?(tag)
         | 
| 588 | 
            +
                #     grouped_entries[tag].push(arr)
         | 
| 589 | 
            +
                #   end
         | 
| 590 | 
            +
                #
         | 
| 591 | 
            +
                #   grouped_entries.each do |tag, arr|
         | 
| 592 | 
            +
                #     entries = []
         | 
| 593 | 
            +
                #     labels = @common_labels.clone
         | 
| 594 | 
            +
                #
         | 
| 595 | 
            +
                #     if @running_cloudfunctions
         | 
| 596 | 
            +
                #       # If the current group of entries is coming from a Cloud Functions
         | 
| 597 | 
            +
                #       # function, the function name can be extracted from the tag.
         | 
| 598 | 
            +
                #       match_data = @cloudfunctions_tag_regexp.match(tag)
         | 
| 599 | 
            +
                #       if match_data
         | 
| 600 | 
            +
                #         # Service name is set to Cloud Functions only for logs actually
         | 
| 601 | 
            +
                #         # coming from a function.
         | 
| 602 | 
            +
                #         @service_name = CLOUDFUNCTIONS_SERVICE
         | 
| 603 | 
            +
                #         labels["#{CLOUDFUNCTIONS_SERVICE}/region"] = @gcf_region
         | 
| 604 | 
            +
                #         labels["#{CLOUDFUNCTIONS_SERVICE}/function_name"] =
         | 
| 605 | 
            +
                #           decode_cloudfunctions_function_name(
         | 
| 606 | 
            +
                #             match_data['encoded_function_name'])
         | 
| 607 | 
            +
                #       else
         | 
| 608 | 
            +
                #         # Other logs are considered as coming from the Container Engine
         | 
| 609 | 
            +
                #         # service.
         | 
| 610 | 
            +
                #         @service_name = CONTAINER_SERVICE
         | 
| 611 | 
            +
                #       end
         | 
| 612 | 
            +
                #     end
         | 
| 613 | 
            +
                #     if @service_name == CONTAINER_SERVICE && \
         | 
| 614 | 
            +
                #        @compiled_kubernetes_tag_regexp
         | 
| 615 | 
            +
                #       # Container logs in Kubernetes are tagged based on where they came
         | 
| 616 | 
            +
                #       # from, so we can extract useful metadata from the tag.
         | 
| 617 | 
            +
                #       # Do this here to avoid having to repeat it for each record.
         | 
| 618 | 
            +
                #       match_data = @compiled_kubernetes_tag_regexp.match(tag)
         | 
| 619 | 
            +
                #       if match_data
         | 
| 620 | 
            +
                #         %w(namespace_name pod_name container_name).each do |field|
         | 
| 621 | 
            +
                #           labels["#{CONTAINER_SERVICE}/#{field}"] = match_data[field]
         | 
| 622 | 
            +
                #         end
         | 
| 623 | 
            +
                #       end
         | 
| 624 | 
            +
                #     end
         | 
| 625 | 
            +
                #
         | 
| 626 | 
            +
                #     if @use_grpc
         | 
| 627 | 
            +
                #       arr.each do |time, record|
         | 
| 628 | 
            +
                #         next unless record.is_a?(Hash)
         | 
| 629 | 
            +
                #
         | 
| 630 | 
            +
                #         entry = Google::Logging::V1::LogEntry.new(
         | 
| 631 | 
            +
                #           metadata: Google::Logging::V1::LogEntryMetadata.new(
         | 
| 632 | 
            +
                #             service_name: @service_name.encode('utf-8'),
         | 
| 633 | 
            +
                #             project_id: @project_id.encode('utf-8'),
         | 
| 634 | 
            +
                #             zone: @zone.encode('utf-8'),
         | 
| 635 | 
            +
                #             labels: {}
         | 
| 636 | 
            +
                #           ))
         | 
| 637 | 
            +
                #
         | 
| 638 | 
            +
                #         if @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
         | 
| 639 | 
            +
                #           @cloudfunctions_log_match =
         | 
| 640 | 
            +
                #             @cloudfunctions_log_regexp.match(record['log'])
         | 
| 641 | 
            +
                #         end
         | 
| 642 | 
            +
                #         if @service_name == CONTAINER_SERVICE
         | 
| 643 | 
            +
                #           # Move the stdout/stderr annotation from the record into a
         | 
| 644 | 
            +
                #           # label.
         | 
| 645 | 
            +
                #           field_to_label(record, 'stream', entry.metadata.labels,
         | 
| 646 | 
            +
                #                          "#{CONTAINER_SERVICE}/stream")
         | 
| 647 | 
            +
                #           # If the record has been annotated by the
         | 
| 648 | 
            +
                #           # kubernetes_metadata_filter
         | 
| 649 | 
            +
                #           # plugin, then use that metadata.  Otherwise, rely on
         | 
| 650 | 
            +
                #           # commonLabels
         | 
| 651 | 
            +
                #           # populated at the grouped_entries level from the group's tag.
         | 
| 652 | 
            +
                #           if record.key?('kubernetes')
         | 
| 653 | 
            +
                #             handle_container_metadata(record, entry)
         | 
| 654 | 
            +
                #           end
         | 
| 655 | 
            +
                #
         | 
| 656 | 
            +
                #           # Save the timestamp if available, then clear it out to allow
         | 
| 657 | 
            +
                #           # for determining whether we should parse the log or message
         | 
| 658 | 
            +
                #           # field.
         | 
| 659 | 
            +
                #           timestamp = record.key?('time') ? record['time'] : nil
         | 
| 660 | 
            +
                #           record.delete('time')
         | 
| 661 | 
            +
                #           # If the log is json, we want to export it as a structured log
         | 
| 662 | 
            +
                #           # unless there is additional metadata that would be lost.
         | 
| 663 | 
            +
                #           is_json = false
         | 
| 664 | 
            +
                #           if record.length == 1 && record.key?('log')
         | 
| 665 | 
            +
                #             record_json = parse_json_or_nil(record['log'])
         | 
| 666 | 
            +
                #           end
         | 
| 667 | 
            +
                #           if record.length == 1 && record.key?('message')
         | 
| 668 | 
            +
                #             record_json = parse_json_or_nil(record['message'])
         | 
| 669 | 
            +
                #           end
         | 
| 670 | 
            +
                #           unless record_json.nil?
         | 
| 671 | 
            +
                #             record = record_json
         | 
| 672 | 
            +
                #             is_json = true
         | 
| 673 | 
            +
                #           end
         | 
| 674 | 
            +
                #           # Restore timestamp if necessary.
         | 
| 675 | 
            +
                #           unless record.key?('time') || timestamp.nil?
         | 
| 676 | 
            +
                #             record['time'] = timestamp
         | 
| 677 | 
            +
                #           end
         | 
| 678 | 
            +
                #         end
         | 
| 679 | 
            +
                #
         | 
| 680 | 
            +
                #         ts_secs, ts_nanos = compute_timestamp(record, time)
         | 
| 681 | 
            +
                #         entry.metadata.timestamp = Google::Protobuf::Timestamp.new(
         | 
| 682 | 
            +
                #           seconds: ts_secs,
         | 
| 683 | 
            +
                #           nanos: ts_nanos
         | 
| 684 | 
            +
                #         )
         | 
| 685 | 
            +
                #
         | 
| 686 | 
            +
                #         entry.metadata.severity =
         | 
| 687 | 
            +
                #           grpc_severity(compute_severity(record, entry))
         | 
| 688 | 
            +
                #
         | 
| 689 | 
            +
                #         set_http_request_grpc(record, entry) # FIXME
         | 
| 690 | 
            +
                #
         | 
| 691 | 
            +
                #         # If a field is present in the label_map, send its value as a
         | 
| 692 | 
            +
                #         # label
         | 
| 693 | 
            +
                #         # (mapping the field name to label name as specified in the
         | 
| 694 | 
            +
                #         # config)
         | 
| 695 | 
            +
                #         # and do not send that field as part of the payload.
         | 
| 696 | 
            +
                #         if @label_map
         | 
| 697 | 
            +
                #           @label_map.each do |field, label|
         | 
| 698 | 
            +
                #             field_to_label(record, field, entry.metadata.labels, label)
         | 
| 699 | 
            +
                #           end
         | 
| 700 | 
            +
                #         end
         | 
| 701 | 
            +
                #
         | 
| 702 | 
            +
                #         if @service_name == CLOUDFUNCTIONS_SERVICE &&
         | 
| 703 | 
            +
                #            @cloudfunctions_log_match &&
         | 
| 704 | 
            +
                #            @cloudfunctions_log_match['execution_id']
         | 
| 705 | 
            +
                #           entry.metadata.labels['execution_id'] =
         | 
| 706 | 
            +
                #             @cloudfunctions_log_match['execution_id']
         | 
| 707 | 
            +
                #         end
         | 
| 708 | 
            +
                #
         | 
| 709 | 
            +
                #         set_payload_grpc(record, entry, is_json)
         | 
| 710 | 
            +
                #
         | 
| 711 | 
            +
                #         entries.push(entry)
         | 
| 712 | 
            +
                #       end
         | 
| 713 | 
            +
                #       # Don't send an empty request if we rejected all the entries.
         | 
| 714 | 
            +
                #       next if entries.empty?
         | 
| 715 | 
            +
                #
         | 
| 716 | 
            +
                #       log_name = log_name(tag, labels)
         | 
| 717 | 
            +
                #
         | 
| 718 | 
            +
                #       begin
         | 
| 719 | 
            +
                #         # Does the actual write to the cloud logging api.
         | 
| 720 | 
            +
                #
         | 
| 721 | 
            +
                #         client = api_client
         | 
| 722 | 
            +
                #
         | 
| 723 | 
            +
                #         labels_utf8_pairs = labels.map do |k, v|
         | 
| 724 | 
            +
                #           [k.encode('utf-8'), v.encode('utf-8')]
         | 
| 725 | 
            +
                #         end
         | 
| 726 | 
            +
                #
         | 
| 727 | 
            +
                #         write_request = Google::Logging::V1::WriteLogEntriesRequest.new(
         | 
| 728 | 
            +
                #           log_name: log_name.encode('utf-8'),
         | 
| 729 | 
            +
                #           common_labels: Hash[labels_utf8_pairs],
         | 
| 730 | 
            +
                #           entries: entries
         | 
| 731 | 
            +
                #         )
         | 
| 732 | 
            +
                #
         | 
| 733 | 
            +
                #         client.write_log_entries(write_request)
         | 
| 734 | 
            +
                #
         | 
| 735 | 
            +
                #         # Let the user explicitly know when the first call succeeded,
         | 
| 736 | 
            +
                #         # to aid with verification and troubleshooting.
         | 
| 737 | 
            +
                #         unless @successful_call
         | 
| 738 | 
            +
                #           @successful_call = true
         | 
| 739 | 
            +
                #           @log.info 'Successfully sent gRPC to Google Cloud Logging API.'
         | 
| 740 | 
            +
                #         end
         | 
| 741 | 
            +
                #
         | 
| 742 | 
            +
                #       rescue GRPC::Cancelled => error
         | 
| 743 | 
            +
                #         # RPC cancelled, so retry via re-raising the error.
         | 
| 744 | 
            +
                #         raise error
         | 
| 745 | 
            +
                #
         | 
| 746 | 
            +
                #       rescue GRPC::BadStatus => error
         | 
| 747 | 
            +
                #         case error.code
         | 
| 748 | 
            +
                #         when GRPC::Core::StatusCodes::CANCELLED,
         | 
| 749 | 
            +
                #              GRPC::Core::StatusCodes::UNAVAILABLE,
         | 
| 750 | 
            +
                #              GRPC::Core::StatusCodes::DEADLINE_EXCEEDED,
         | 
| 751 | 
            +
                #              GRPC::Core::StatusCodes::INTERNAL,
         | 
| 752 | 
            +
                #              GRPC::Core::StatusCodes::UNKNOWN
         | 
| 753 | 
            +
                #           # TODO
         | 
| 754 | 
            +
                #           # Server error, so retry via re-raising the error.
         | 
| 755 | 
            +
                #           raise error
         | 
| 756 | 
            +
                #         when GRPC::Core::StatusCodes::UNIMPLEMENTED,
         | 
| 757 | 
            +
                #              GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED
         | 
| 758 | 
            +
                #           # Most client errors indicate a problem with the request itself
         | 
| 759 | 
            +
                #           # and should not be retried.
         | 
| 760 | 
            +
                #           dropped = entries.length
         | 
| 761 | 
            +
                #           @log.warn "Dropping #{dropped} log message(s)",
         | 
| 762 | 
            +
                #                     error: error.to_s, error_code: error.code.to_s
         | 
| 763 | 
            +
                #         when GRPC::Core::StatusCodes::UNAUTHENTICATED
         | 
| 764 | 
            +
                #           # Authorization error.
         | 
| 765 | 
            +
                #           # These are usually solved via a `gcloud auth` call, or by
         | 
| 766 | 
            +
                #           # modifying the permissions on the Google Cloud project.
         | 
| 767 | 
            +
                #           dropped = entries.length
         | 
| 768 | 
            +
                #           @log.warn "Dropping #{dropped} log message(s)",
         | 
| 769 | 
            +
                #                     error: error.to_s, error_code: error.code.to_s
         | 
| 770 | 
            +
                #         else
         | 
| 771 | 
            +
                #           @log.error "Unknown response code #{error.code} from the " \
         | 
| 772 | 
            +
                #                      "server",
         | 
| 773 | 
            +
                #                      error: error.to_s, error_code: error.code.to_s
         | 
| 774 | 
            +
                #         end
         | 
| 775 | 
            +
                #       end
         | 
| 776 | 
            +
                #     else
         | 
| 777 | 
            +
                #       arr.each do |time, record|
         | 
| 778 | 
            +
                #         next unless record.is_a?(Hash)
         | 
| 779 | 
            +
                #
         | 
| 780 | 
            +
                #         entry = Google::Apis::LoggingV1beta3::LogEntry.new(
         | 
| 781 | 
            +
                #           metadata: Google::Apis::LoggingV1beta3::LogEntryMetadata.new(
         | 
| 782 | 
            +
                #             service_name: @service_name,
         | 
| 783 | 
            +
                #             project_id: @project_id,
         | 
| 784 | 
            +
                #             zone: @zone,
         | 
| 785 | 
            +
                #             labels: {}
         | 
| 786 | 
            +
                #           ))
         | 
| 787 | 
            +
                #
         | 
| 788 | 
            +
                #         if @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
         | 
| 789 | 
            +
                #           @cloudfunctions_log_match =
         | 
| 790 | 
            +
                #             @cloudfunctions_log_regexp.match(record['log'])
         | 
| 791 | 
            +
                #         end
         | 
| 792 | 
            +
                #         if @service_name == CONTAINER_SERVICE
         | 
| 793 | 
            +
                #           # Move the stdout/stderr annotation from the record into a label
         | 
| 794 | 
            +
                #           field_to_label(record, 'stream', entry.metadata.labels,
         | 
| 795 | 
            +
                #                          "#{CONTAINER_SERVICE}/stream")
         | 
| 796 | 
            +
                #           # If the record has been annotated by the
         | 
| 797 | 
            +
                #           # kubernetes_metadata_filter
         | 
| 798 | 
            +
                #           # plugin, then use that metadata. Otherwise, rely on
         | 
| 799 | 
            +
                #           # commonLabels
         | 
| 800 | 
            +
                #           # populated at the grouped_entries level from the group's tag.
         | 
| 801 | 
            +
                #           if record.key?('kubernetes')
         | 
| 802 | 
            +
                #             handle_container_metadata(record, entry)
         | 
| 803 | 
            +
                #           end
         | 
| 804 | 
            +
                #
         | 
| 805 | 
            +
                #           # Save the timestamp if available, then clear it out to allow
         | 
| 806 | 
            +
                #           # for determining whether we should parse the log or message
         | 
| 807 | 
            +
                #           # field.
         | 
| 808 | 
            +
                #           timestamp = record.key?('time') ? record['time'] : nil
         | 
| 809 | 
            +
                #           record.delete('time')
         | 
| 810 | 
            +
                #           # If the log is json, we want to export it as a structured log
         | 
| 811 | 
            +
                #           # unless there is additional metadata that would be lost.
         | 
| 812 | 
            +
                #           is_json = false
         | 
| 813 | 
            +
                #           if record.length == 1 && record.key?('log')
         | 
| 814 | 
            +
                #             record_json = parse_json_or_nil(record['log'])
         | 
| 815 | 
            +
                #           end
         | 
| 816 | 
            +
                #           if record.length == 1 && record.key?('message')
         | 
| 817 | 
            +
                #             record_json = parse_json_or_nil(record['message'])
         | 
| 818 | 
            +
                #           end
         | 
| 819 | 
            +
                #           unless record_json.nil?
         | 
| 820 | 
            +
                #             record = record_json
         | 
| 821 | 
            +
                #             is_json = true
         | 
| 822 | 
            +
                #           end
         | 
| 823 | 
            +
                #           # Restore timestamp if necessary.
         | 
| 824 | 
            +
                #           unless record.key?('time') || timestamp.nil?
         | 
| 825 | 
            +
                #             record['time'] = timestamp
         | 
| 826 | 
            +
                #           end
         | 
| 827 | 
            +
                #         end
         | 
| 828 | 
            +
                #
         | 
| 829 | 
            +
                #         ts_secs, ts_nanos = compute_timestamp(record, time)
         | 
| 830 | 
            +
                #         entry.metadata.timestamp = {
         | 
| 831 | 
            +
                #           seconds: ts_secs,
         | 
| 832 | 
            +
                #           nanos: ts_nanos
         | 
| 833 | 
            +
                #         }
         | 
| 834 | 
            +
                #
         | 
| 835 | 
            +
                #         entry.metadata.severity = compute_severity(record, entry)
         | 
| 836 | 
            +
                #
         | 
| 837 | 
            +
                #         set_http_request(record, entry)
         | 
| 838 | 
            +
                #
         | 
| 839 | 
            +
                #         # If a field is present in the label_map, send its value as a
         | 
| 840 | 
            +
                #         # label
         | 
| 841 | 
            +
                #         # (mapping the field name to label name as specified in the
         | 
| 842 | 
            +
                #         # config)
         | 
| 843 | 
            +
                #         # and do not send that field as part of the payload.
         | 
| 844 | 
            +
                #         if @label_map
         | 
| 845 | 
            +
                #           @label_map.each do |field, label|
         | 
| 846 | 
            +
                #             field_to_label(record, field, entry.metadata.labels, label)
         | 
| 847 | 
            +
                #           end
         | 
| 848 | 
            +
                #         end
         | 
| 849 | 
            +
                #
         | 
| 850 | 
            +
                #         if @service_name == CLOUDFUNCTIONS_SERVICE &&
         | 
| 851 | 
            +
                #            @cloudfunctions_log_match &&
         | 
| 852 | 
            +
                #            @cloudfunctions_log_match['execution_id']
         | 
| 853 | 
            +
                #           entry.metadata.labels['execution_id'] =
         | 
| 854 | 
            +
                #             @cloudfunctions_log_match['execution_id']
         | 
| 855 | 
            +
                #         end
         | 
| 856 | 
            +
                #
         | 
| 857 | 
            +
                #         set_payload(record, entry, is_json)
         | 
| 858 | 
            +
                #         entry.metadata.labels = nil if entry.metadata.labels.empty?
         | 
| 859 | 
            +
                #
         | 
| 860 | 
            +
                #         entries.push(entry)
         | 
| 861 | 
            +
                #       end
         | 
| 862 | 
            +
                #       # Don't send an empty request if we rejected all the entries.
         | 
| 863 | 
            +
                #       next if entries.empty?
         | 
| 864 | 
            +
                #
         | 
| 865 | 
            +
                #       log_name = log_name(tag, labels)
         | 
| 866 | 
            +
                #
         | 
| 867 | 
            +
                #       begin
         | 
| 868 | 
            +
                #         # Does the actual write to the cloud logging api.
         | 
| 869 | 
            +
                #         # The URI of the write is constructed by the Google::Api request;
         | 
| 870 | 
            +
                #         # it is equivalent to this URL:
         | 
| 871 | 
            +
                #         # 'https://logging.googleapis.com/v1beta3/projects/' \
         | 
| 872 | 
            +
                #         #   "#{@project_id}/logs/#{log_name}/entries:write"
         | 
| 873 | 
            +
                #
         | 
| 874 | 
            +
                #         client = api_client
         | 
| 875 | 
            +
                #
         | 
| 876 | 
            +
                #         write_request = \
         | 
| 877 | 
            +
                #           Google::Apis::LoggingV1beta3::WriteLogEntriesRequest.new(
         | 
| 878 | 
            +
                #             common_labels: labels,
         | 
| 879 | 
            +
                #             entries: entries)
         | 
| 880 | 
            +
                #
         | 
| 881 | 
            +
                #         # TODO: RequestOptions
         | 
| 882 | 
            +
                #         client.write_log_entries(@project_id, log_name, write_request)
         | 
| 883 | 
            +
                #
         | 
| 884 | 
            +
                #         # Let the user explicitly know when the first call succeeded,
         | 
| 885 | 
            +
                #         # to aid with verification and troubleshooting.
         | 
| 886 | 
            +
                #         unless @successful_call
         | 
| 887 | 
            +
                #           @successful_call = true
         | 
| 888 | 
            +
                #           @log.info 'Successfully sent to Google Cloud Logging API.'
         | 
| 889 | 
            +
                #         end
         | 
| 890 | 
            +
                #
         | 
| 891 | 
            +
                #       rescue Google::Apis::ServerError => error
         | 
| 892 | 
            +
                #         # Server error, so retry via re-raising the error.
         | 
| 893 | 
            +
                #         raise error
         | 
| 894 | 
            +
                #
         | 
| 895 | 
            +
                #       rescue Google::Apis::AuthorizationError => error
         | 
| 896 | 
            +
                #         # Authorization error.
         | 
| 897 | 
            +
                #         # These are usually solved via a `gcloud auth` call, or by
         | 
| 898 | 
            +
                #         # modifying
         | 
| 899 | 
            +
                #         # the permissions on the Google Cloud project.
         | 
| 900 | 
            +
                #         dropped = entries.length
         | 
| 901 | 
            +
                #         @log.warn "Dropping #{dropped} log message(s)",
         | 
| 902 | 
            +
                #                   error_class: error.class.to_s, error: error.to_s
         | 
| 903 | 
            +
                #
         | 
| 904 | 
            +
                #       rescue Google::Apis::ClientError => error
         | 
| 905 | 
            +
                #         # Most ClientErrors indicate a problem with the request itself and
         | 
| 906 | 
            +
                #         # should not be retried.
         | 
| 907 | 
            +
                #         dropped = entries.length
         | 
| 908 | 
            +
                #         @log.warn "Dropping #{dropped} log message(s)",
         | 
| 909 | 
            +
                #                   error_class: error.class.to_s, error: error.to_s
         | 
| 910 | 
            +
                #       end
         | 
| 911 | 
            +
                #     end
         | 
| 912 | 
            +
                #   end
         | 
| 913 | 
            +
                # end
         | 
| 914 | 
            +
             | 
| 476 915 | 
             
                private
         | 
| 477 916 |  | 
| 478 917 | 
             
                def parse_json_or_nil(input)
         | 
| @@ -602,7 +1041,7 @@ module Fluent | |
| 602 1041 | 
             
                  instance_prefix
         | 
| 603 1042 | 
             
                end
         | 
| 604 1043 |  | 
| 605 | 
            -
                def  | 
| 1044 | 
            +
                def compute_timestamp(record, time)
         | 
| 606 1045 | 
             
                  if record.key?('timestamp') &&
         | 
| 607 1046 | 
             
                     record['timestamp'].is_a?(Hash) &&
         | 
| 608 1047 | 
             
                     record['timestamp'].key?('seconds') &&
         | 
| @@ -645,41 +1084,36 @@ module Fluent | |
| 645 1084 | 
             
                    ts_secs = timestamp.tv_sec
         | 
| 646 1085 | 
             
                    ts_nanos = timestamp.tv_nsec
         | 
| 647 1086 | 
             
                  end
         | 
| 648 | 
            -
                   | 
| 649 | 
            -
                    seconds: ts_secs,
         | 
| 650 | 
            -
                    nanos: ts_nanos
         | 
| 651 | 
            -
                  }
         | 
| 1087 | 
            +
                  [ts_secs, ts_nanos]
         | 
| 652 1088 | 
             
                end
         | 
| 653 1089 |  | 
| 654 | 
            -
                def  | 
| 1090 | 
            +
                def compute_severity(record, entry)
         | 
| 655 1091 | 
             
                  if @service_name == CLOUDFUNCTIONS_SERVICE
         | 
| 656 1092 | 
             
                    if @cloudfunctions_log_match && @cloudfunctions_log_match['severity']
         | 
| 657 | 
            -
                       | 
| 658 | 
            -
                        parse_severity(@cloudfunctions_log_match['severity'])
         | 
| 1093 | 
            +
                      return parse_severity(@cloudfunctions_log_match['severity'])
         | 
| 659 1094 | 
             
                    elsif record.key?('stream') && record['stream'] == 'stdout'
         | 
| 660 | 
            -
                      entry.metadata.severity = 'INFO'
         | 
| 661 1095 | 
             
                      record.delete('stream')
         | 
| 1096 | 
            +
                      return 'INFO'
         | 
| 662 1097 | 
             
                    elsif record.key?('stream') && record['stream'] == 'stderr'
         | 
| 663 | 
            -
                      entry.metadata.severity = 'ERROR'
         | 
| 664 1098 | 
             
                      record.delete('stream')
         | 
| 1099 | 
            +
                      return 'ERROR'
         | 
| 665 1100 | 
             
                    else
         | 
| 666 | 
            -
                       | 
| 1101 | 
            +
                      return 'DEFAULT'
         | 
| 667 1102 | 
             
                    end
         | 
| 668 1103 | 
             
                  elsif record.key?('severity')
         | 
| 669 | 
            -
                     | 
| 670 | 
            -
                    record.delete('severity')
         | 
| 1104 | 
            +
                    return parse_severity(record.delete('severity'))
         | 
| 671 1105 | 
             
                  elsif @service_name == CONTAINER_SERVICE && \
         | 
| 672 1106 | 
             
                        entry.metadata.labels.key?("#{CONTAINER_SERVICE}/stream")
         | 
| 673 1107 | 
             
                    stream = entry.metadata.labels["#{CONTAINER_SERVICE}/stream"]
         | 
| 674 1108 | 
             
                    if stream == 'stdout'
         | 
| 675 | 
            -
                       | 
| 1109 | 
            +
                      return 'INFO'
         | 
| 676 1110 | 
             
                    elsif stream == 'stderr'
         | 
| 677 | 
            -
                       | 
| 1111 | 
            +
                      return 'ERROR'
         | 
| 678 1112 | 
             
                    else
         | 
| 679 | 
            -
                       | 
| 1113 | 
            +
                      return 'DEFAULT'
         | 
| 680 1114 | 
             
                    end
         | 
| 681 1115 | 
             
                  else
         | 
| 682 | 
            -
                     | 
| 1116 | 
            +
                    return 'DEFAULT'
         | 
| 683 1117 | 
             
                  end
         | 
| 684 1118 | 
             
                end
         | 
| 685 1119 |  | 
| @@ -702,6 +1136,25 @@ module Fluent | |
| 702 1136 | 
             
                  entry.http_request = output
         | 
| 703 1137 | 
             
                end
         | 
| 704 1138 |  | 
| 1139 | 
            +
                def set_http_request_grpc(record, entry)
         | 
| 1140 | 
            +
                  return nil unless record['httpRequest'].is_a?(Hash)
         | 
| 1141 | 
            +
                  input = record['httpRequest']
         | 
| 1142 | 
            +
                  output = Google::Logging::Type::HttpRequest.new
         | 
| 1143 | 
            +
                  output.request_method = input.delete('requestMethod')
         | 
| 1144 | 
            +
                  output.request_url = input.delete('requestUrl')
         | 
| 1145 | 
            +
                  output.request_size = input.delete('requestSize').to_i
         | 
| 1146 | 
            +
                  output.status = input.delete('status').to_i
         | 
| 1147 | 
            +
                  output.response_size = input.delete('responseSize').to_i
         | 
| 1148 | 
            +
                  output.user_agent = input.delete('userAgent')
         | 
| 1149 | 
            +
                  output.remote_ip = input.delete('remoteIp')
         | 
| 1150 | 
            +
                  output.referer = input.delete('referer')
         | 
| 1151 | 
            +
                  output.cache_hit = input.delete('cacheHit') == 'true'
         | 
| 1152 | 
            +
                  output.validated_with_origin_server = \
         | 
| 1153 | 
            +
                    input.delete('validatedWithOriginServer') == 'true'
         | 
| 1154 | 
            +
                  record.delete('httpRequest') if input.empty?
         | 
| 1155 | 
            +
                  entry.http_request = output
         | 
| 1156 | 
            +
                end
         | 
| 1157 | 
            +
             | 
| 705 1158 | 
             
                # Values permitted by the API for 'severity' (which is an enum).
         | 
| 706 1159 | 
             
                VALID_SEVERITIES = Set.new(
         | 
| 707 1160 | 
             
                  %w(DEFAULT DEBUG INFO NOTICE WARNING ERROR CRITICAL ALERT EMERGENCY))
         | 
| @@ -762,6 +1215,38 @@ module Fluent | |
| 762 1215 | 
             
                  'DEFAULT'
         | 
| 763 1216 | 
             
                end
         | 
| 764 1217 |  | 
| 1218 | 
            +
                GRPC_SEVERITY_MAPPING = {
         | 
| 1219 | 
            +
                  'DEFAULT' => Google::Logging::Type::LogSeverity::DEFAULT,
         | 
| 1220 | 
            +
                  'DEBUG' => Google::Logging::Type::LogSeverity::DEBUG,
         | 
| 1221 | 
            +
                  'INFO' => Google::Logging::Type::LogSeverity::INFO,
         | 
| 1222 | 
            +
                  'NOTICE' => Google::Logging::Type::LogSeverity::NOTICE,
         | 
| 1223 | 
            +
                  'WARNING' => Google::Logging::Type::LogSeverity::WARNING,
         | 
| 1224 | 
            +
                  'ERROR' => Google::Logging::Type::LogSeverity::ERROR,
         | 
| 1225 | 
            +
                  'CRITICAL' => Google::Logging::Type::LogSeverity::CRITICAL,
         | 
| 1226 | 
            +
                  'ALERT' => Google::Logging::Type::LogSeverity::ALERT,
         | 
| 1227 | 
            +
                  'EMERGENCY' => Google::Logging::Type::LogSeverity::EMERGENCY,
         | 
| 1228 | 
            +
                  0 => Google::Logging::Type::LogSeverity::DEFAULT,
         | 
| 1229 | 
            +
                  100 => Google::Logging::Type::LogSeverity::DEBUG,
         | 
| 1230 | 
            +
                  200 => Google::Logging::Type::LogSeverity::INFO,
         | 
| 1231 | 
            +
                  300 => Google::Logging::Type::LogSeverity::NOTICE,
         | 
| 1232 | 
            +
                  400 => Google::Logging::Type::LogSeverity::WARNING,
         | 
| 1233 | 
            +
                  500 => Google::Logging::Type::LogSeverity::ERROR,
         | 
| 1234 | 
            +
                  600 => Google::Logging::Type::LogSeverity::CRITICAL,
         | 
| 1235 | 
            +
                  700 => Google::Logging::Type::LogSeverity::ALERT,
         | 
| 1236 | 
            +
                  800 => Google::Logging::Type::LogSeverity::EMERGENCY
         | 
| 1237 | 
            +
                }
         | 
| 1238 | 
            +
             | 
| 1239 | 
            +
                def grpc_severity(severity)
         | 
| 1240 | 
            +
                  # TODO: find out why this doesn't work.
         | 
| 1241 | 
            +
                  # if severity.is_a? String
         | 
| 1242 | 
            +
                  #   return Google::Logging::Type::LogSeverity.resolve(severity)
         | 
| 1243 | 
            +
                  # end
         | 
| 1244 | 
            +
                  if GRPC_SEVERITY_MAPPING.key?(severity)
         | 
| 1245 | 
            +
                    return GRPC_SEVERITY_MAPPING[severity]
         | 
| 1246 | 
            +
                  end
         | 
| 1247 | 
            +
                  severity
         | 
| 1248 | 
            +
                end
         | 
| 1249 | 
            +
             | 
| 765 1250 | 
             
                def decode_cloudfunctions_function_name(function_name)
         | 
| 766 1251 | 
             
                  function_name.gsub(/c\.[a-z]/) { |s| s.upcase[-1] }
         | 
| 767 1252 | 
             
                    .gsub('u.u', '_').gsub('d.d', '$').gsub('a.a', '@').gsub('p.p', '.')
         | 
| @@ -815,6 +1300,72 @@ module Fluent | |
| 815 1300 | 
             
                  end
         | 
| 816 1301 | 
             
                end
         | 
| 817 1302 |  | 
| 1303 | 
            +
                def value_from_ruby(value)
         | 
| 1304 | 
            +
                  ret = Google::Protobuf::Value.new
         | 
| 1305 | 
            +
                  case value
         | 
| 1306 | 
            +
                  when NilClass
         | 
| 1307 | 
            +
                    ret.null_value = 0
         | 
| 1308 | 
            +
                  when Numeric
         | 
| 1309 | 
            +
                    ret.number_value = value
         | 
| 1310 | 
            +
                  when String
         | 
| 1311 | 
            +
                    ret.string_value = value.encode('utf-8')
         | 
| 1312 | 
            +
                  when TrueClass
         | 
| 1313 | 
            +
                    ret.bool_value = true
         | 
| 1314 | 
            +
                  when FalseClass
         | 
| 1315 | 
            +
                    ret.bool_value = false
         | 
| 1316 | 
            +
                  when Google::Protobuf::Struct
         | 
| 1317 | 
            +
                    ret.struct_value = value
         | 
| 1318 | 
            +
                  when Hash
         | 
| 1319 | 
            +
                    ret.struct_value = struct_from_ruby(value)
         | 
| 1320 | 
            +
                  when Google::Protobuf::ListValue
         | 
| 1321 | 
            +
                    ret.list_value = value
         | 
| 1322 | 
            +
                  when Array
         | 
| 1323 | 
            +
                    ret.list_value = list_from_ruby(value)
         | 
| 1324 | 
            +
                  else
         | 
| 1325 | 
            +
                    @log.error "Unknown type: #{value.class}"
         | 
| 1326 | 
            +
                    fail Google::Protobuf::Error, "Unknown type: #{value.class}"
         | 
| 1327 | 
            +
                  end
         | 
| 1328 | 
            +
                  ret
         | 
| 1329 | 
            +
                end
         | 
| 1330 | 
            +
             | 
| 1331 | 
            +
                def list_from_ruby(arr)
         | 
| 1332 | 
            +
                  ret = Google::Protobuf::ListValue.new
         | 
| 1333 | 
            +
                  arr.each do |v|
         | 
| 1334 | 
            +
                    ret.values << value_from_ruby(v)
         | 
| 1335 | 
            +
                  end
         | 
| 1336 | 
            +
                  ret
         | 
| 1337 | 
            +
                end
         | 
| 1338 | 
            +
             | 
| 1339 | 
            +
                def struct_from_ruby(hash)
         | 
| 1340 | 
            +
                  ret = Google::Protobuf::Struct.new
         | 
| 1341 | 
            +
                  hash.each do |k, v|
         | 
| 1342 | 
            +
                    ret.fields[k] ||= value_from_ruby(v)
         | 
| 1343 | 
            +
                  end
         | 
| 1344 | 
            +
                  ret
         | 
| 1345 | 
            +
                end
         | 
| 1346 | 
            +
             | 
| 1347 | 
            +
                def set_payload_grpc(record, entry, is_json)
         | 
| 1348 | 
            +
                  # If this is a Cloud Functions log that matched the expected regexp,
         | 
| 1349 | 
            +
                  # use text payload. Otherwise, use JSON if we found valid JSON, or text
         | 
| 1350 | 
            +
                  # payload in the following cases:
         | 
| 1351 | 
            +
                  # 1. This is a Cloud Functions log and the 'log' key is available
         | 
| 1352 | 
            +
                  # 2. This is an unstructured Container log and the 'log' key is available
         | 
| 1353 | 
            +
                  # 3. The only remaining key is 'message'
         | 
| 1354 | 
            +
                  if @service_name == CLOUDFUNCTIONS_SERVICE && @cloudfunctions_log_match
         | 
| 1355 | 
            +
                    entry.text_payload = @cloudfunctions_log_match['text']
         | 
| 1356 | 
            +
                  elsif @service_name == CLOUDFUNCTIONS_SERVICE && record.key?('log')
         | 
| 1357 | 
            +
                    entry.text_payload = record['log']
         | 
| 1358 | 
            +
                  elsif is_json
         | 
| 1359 | 
            +
                    entry.struct_payload = struct_from_ruby(record)
         | 
| 1360 | 
            +
                  elsif @service_name == CONTAINER_SERVICE && record.key?('log')
         | 
| 1361 | 
            +
                    entry.text_payload = record['log']
         | 
| 1362 | 
            +
                  elsif record.size == 1 && record.key?('message')
         | 
| 1363 | 
            +
                    entry.text_payload = record['message']
         | 
| 1364 | 
            +
                  else
         | 
| 1365 | 
            +
                    entry.struct_payload = struct_from_ruby(record)
         | 
| 1366 | 
            +
                  end
         | 
| 1367 | 
            +
                end
         | 
| 1368 | 
            +
             | 
| 818 1369 | 
             
                def log_name(tag, common_labels)
         | 
| 819 1370 | 
             
                  if @service_name == CLOUDFUNCTIONS_SERVICE
         | 
| 820 1371 | 
             
                    return 'cloud-functions'
         | 
| @@ -833,6 +1384,7 @@ module Fluent | |
| 833 1384 | 
             
                end
         | 
| 834 1385 |  | 
| 835 1386 | 
             
                def init_api_client
         | 
| 1387 | 
            +
                  return if @use_grpc
         | 
| 836 1388 | 
             
                  # TODO: Use a non-default ClientOptions object.
         | 
| 837 1389 | 
             
                  Google::Apis::ClientOptions.default.application_name = PLUGIN_NAME
         | 
| 838 1390 | 
             
                  Google::Apis::ClientOptions.default.application_version = PLUGIN_VERSION
         | 
| @@ -842,14 +1394,23 @@ module Fluent | |
| 842 1394 | 
             
                end
         | 
| 843 1395 |  | 
| 844 1396 | 
             
                def api_client
         | 
| 845 | 
            -
                   | 
| 846 | 
            -
                     | 
| 847 | 
            -
             | 
| 848 | 
            -
                     | 
| 849 | 
            -
             | 
| 850 | 
            -
             | 
| 851 | 
            -
                       | 
| 852 | 
            -
             | 
| 1397 | 
            +
                  if @use_grpc
         | 
| 1398 | 
            +
                    ssl_creds = GRPC::Core::ChannelCredentials.new
         | 
| 1399 | 
            +
                    authentication = Google::Auth.get_application_default
         | 
| 1400 | 
            +
                    creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
         | 
| 1401 | 
            +
                    creds = ssl_creds.compose(creds)
         | 
| 1402 | 
            +
                    @client = Google::Logging::V1::LoggingService::Stub.new(
         | 
| 1403 | 
            +
                      'logging.googleapis.com', creds)
         | 
| 1404 | 
            +
                  else
         | 
| 1405 | 
            +
                    unless @client.authorization.expired?
         | 
| 1406 | 
            +
                      begin
         | 
| 1407 | 
            +
                        @client.authorization.fetch_access_token!
         | 
| 1408 | 
            +
                      rescue MultiJson::ParseError
         | 
| 1409 | 
            +
                        # Workaround an issue in the API client; just re-raise a more
         | 
| 1410 | 
            +
                        # descriptive error for the user (which will still cause a retry).
         | 
| 1411 | 
            +
                        raise Google::APIClient::ClientError, 'Unable to fetch access ' \
         | 
| 1412 | 
            +
                          'token (no scopes configured?)'
         | 
| 1413 | 
            +
                      end
         | 
| 853 1414 | 
             
                    end
         | 
| 854 1415 | 
             
                  end
         | 
| 855 1416 | 
             
                  @client
         |