lib/elastic_apm/transport/serializers/metadata_serializer.rb



# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

# frozen_string_literal: true

module ElasticAPM
  module Transport
    module Serializers
      # @api private
      class MetadataSerializer < Serializer
        def build(metadata)
          base =
            {
              service: build_service(metadata.service),
              process: build_process(metadata.process),
              system: build_system(metadata.system),
              labels: build_labels(metadata.labels)
            }

          if metadata.cloud.provider
            base[:cloud] = build_cloud(metadata.cloud)
          end

          { metadata: base }
        end

        private

        def build_service(service)
          base =
            {
              name: keyword_field(service.name),
              environment: keyword_field(service.environment),
              version: keyword_field(service.version),
              agent: {
                name: keyword_field(service.agent.name),
                version: keyword_field(service.agent.version)
              },
              framework: {
                name: keyword_field(service.framework.name),
                version: keyword_field(service.framework.version)
              },
              language: {
                name: keyword_field(service.language.name),
                version: keyword_field(service.language.version)
              },
              runtime: {
                name: keyword_field(service.runtime.name),
                version: keyword_field(service.runtime.version)
              }
            }

          if (node_name = service.node_name)
            base[:node] = { configured_name: keyword_field(node_name) }
          end

          base
        end

        def build_process(process)
          {
            pid: process.pid,
            title: keyword_field(process.title),
            argv: process.argv
          }
        end

        def build_system(system)
          {
            detected_hostname: keyword_field(system.detected_hostname),
            configured_hostname: keyword_field(system.configured_hostname),
            architecture: keyword_field(system.architecture),
            platform: keyword_field(system.platform),
            kubernetes: keyword_object(system.kubernetes),
            container: keyword_object(system.container)
          }
        end

        def build_cloud(cloud)
          strip_nulls!(
            provider: cloud.provider,
            account: {
              id: keyword_field(cloud.account_id),
              name: keyword_field(cloud.account_name)
            },
            availability_zone: keyword_field(cloud.availability_zone),
            instance: {
              id: keyword_field(cloud.instance_id),
              name: keyword_field(cloud.instance_name)
            },
            machine: { type: keyword_field(cloud.machine_type) },
            project: {
              id: keyword_field(cloud.project_id),
              name: keyword_field(cloud.project_name)
            },
            region: keyword_field(cloud.region)
          )
        end

        def build_labels(labels)
          keyword_object(labels)
        end

        # A bug in APM Server 7.9 disallows null values in `cloud`
        def strip_nulls!(hash)
          hash.each_key do |key|
            case value = hash[key]
            when Hash
              strip_nulls!(value)
              hash.delete(key) if value.empty?
            when nil then hash.delete(key)
            end
          end

          hash
        end
      end
    end
  end
end