/home/mip/mip/public/img/credit/datatables/ClientSideMonitoring.tar
ApiCallMonitoringMiddleware.php000064400000011271151520663060012634 0ustar00<?php

namespace Aws\ClientSideMonitoring;

use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Aws\MonitoringEventsInterface;
use Aws\ResultInterface;
use Psr\Http\Message\RequestInterface;

/**
 * @internal
 */
class ApiCallMonitoringMiddleware extends AbstractMonitoringMiddleware
{

    /**
     * Api Call Attempt event keys for each Api Call event key
     *
     * @var array
     */
    private static $eventKeys = [
        'FinalAwsException' => 'AwsException',
        'FinalAwsExceptionMessage' => 'AwsExceptionMessage',
        'FinalSdkException' => 'SdkException',
        'FinalSdkExceptionMessage' => 'SdkExceptionMessage',
        'FinalHttpStatusCode' => 'HttpStatusCode',
    ];

    /**
     * Standard middleware wrapper function with CSM options passed in.
     *
     * @param callable $credentialProvider
     * @param mixed  $options
     * @param string $region
     * @param string $service
     * @return callable
     */
    public static function wrap(
        callable $credentialProvider,
        $options,
        $region,
        $service
    ) {
        return function (callable $handler) use (
            $credentialProvider,
            $options,
            $region,
            $service
        ) {
            return new static(
                $handler,
                $credentialProvider,
                $options,
                $region,
                $service
            );
        };
    }

    /**
     * {@inheritdoc}
     */
    public static function getRequestData(RequestInterface $request)
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public static function getResponseData($klass)
    {
        if ($klass instanceof ResultInterface) {
            $data = [
                'AttemptCount' => self::getResultAttemptCount($klass),
                'MaxRetriesExceeded' => 0,
            ];
        } elseif ($klass instanceof \Exception) {
            $data = [
                'AttemptCount' => self::getExceptionAttemptCount($klass),
                'MaxRetriesExceeded' => self::getMaxRetriesExceeded($klass),
            ];
        } else {
            throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface or Exception.');
        }

        return $data + self::getFinalAttemptData($klass);
    }

    private static function getResultAttemptCount(ResultInterface $result) {
        if (isset($result['@metadata']['transferStats']['http'])) {
            return count($result['@metadata']['transferStats']['http']);
        }
        return 1;
    }

    private static function getExceptionAttemptCount(\Exception $e) {
        $attemptCount = 0;
        if ($e instanceof MonitoringEventsInterface) {
            foreach ($e->getMonitoringEvents() as $event) {
                if (isset($event['Type']) &&
                    $event['Type'] === 'ApiCallAttempt') {
                    $attemptCount++;
                }
            }

        }
        return $attemptCount;
    }

    private static function getFinalAttemptData($klass)
    {
        $data = [];
        if ($klass instanceof MonitoringEventsInterface) {
            $finalAttempt = self::getFinalAttempt($klass->getMonitoringEvents());

            if (!empty($finalAttempt)) {
                foreach (self::$eventKeys as $callKey => $attemptKey) {
                    if (isset($finalAttempt[$attemptKey])) {
                        $data[$callKey] = $finalAttempt[$attemptKey];
                    }
                }
            }
        }

        return $data;
    }

    private static function getFinalAttempt(array $events)
    {
        for (end($events); key($events) !== null; prev($events)) {
            $current = current($events);
            if (isset($current['Type'])
                && $current['Type'] === 'ApiCallAttempt'
            ) {
                return $current;
            }
        }

        return null;
    }

    private static function getMaxRetriesExceeded($klass)
    {
        if ($klass instanceof AwsException && $klass->isMaxRetriesExceeded()) {
            return 1;
        }
        return 0;
    }

    /**
     * {@inheritdoc}
     */
    protected function populateRequestEventData(
        CommandInterface $cmd,
        RequestInterface $request,
        array $event
    ) {
        $event = parent::populateRequestEventData($cmd, $request, $event);
        $event['Type'] = 'ApiCall';
        return $event;
    }

    /**
     * {@inheritdoc}
     */
    protected function populateResultEventData(
        $result,
        array $event
    ) {
        $event = parent::populateResultEventData($result, $event);
        $event['Latency'] = (int) (floor(microtime(true) * 1000) - $event['Timestamp']);
        return $event;
    }
}
AbstractMonitoringMiddleware.php000064400000021145151520663060013073 0ustar00<?php

namespace Aws\ClientSideMonitoring;

use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Aws\MonitoringEventsInterface;
use Aws\ResponseContainerInterface;
use Aws\ResultInterface;
use GuzzleHttp\Promise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal
 */
abstract class AbstractMonitoringMiddleware
    implements MonitoringMiddlewareInterface
{
    private static $socket;

    private $nextHandler;
    private $options;
    protected $credentialProvider;
    protected $region;
    protected $service;

    protected static function getAwsExceptionHeader(AwsException $e, $headerName)
    {
        $response = $e->getResponse();
        if ($response !== null) {
            $header = $response->getHeader($headerName);
            if (!empty($header[0])) {
                return $header[0];
            }
        }
        return null;
    }

    protected static function getResultHeader(ResultInterface $result, $headerName)
    {
        if (isset($result['@metadata']['headers'][$headerName])) {
            return $result['@metadata']['headers'][$headerName];
        }
        return null;
    }

    protected static function getExceptionHeader(\Exception $e, $headerName)
    {
        if ($e instanceof ResponseContainerInterface) {
            $response = $e->getResponse();
            if ($response instanceof ResponseInterface) {
                $header = $response->getHeader($headerName);
                if (!empty($header[0])) {
                    return $header[0];
                }
            }
        }
        return null;
    }

    /**
     * Constructor stores the passed in handler and options.
     *
     * @param callable $handler
     * @param callable $credentialProvider
     * @param $options
     * @param $region
     * @param $service
     */
    public function __construct(
        callable $handler,
        callable $credentialProvider,
        $options,
        $region,
        $service
    ) {
        $this->nextHandler = $handler;
        $this->credentialProvider = $credentialProvider;
        $this->options = $options;
        $this->region = $region;
        $this->service = $service;
    }

    /**
     * Standard invoke pattern for middleware execution to be implemented by
     * child classes.
     *
     * @param  CommandInterface $cmd
     * @param  RequestInterface $request
     * @return Promise\PromiseInterface
     */
    public function __invoke(CommandInterface $cmd, RequestInterface $request)
    {
        $handler = $this->nextHandler;
        $eventData = null;
        $enabled = $this->isEnabled();

        if ($enabled) {
            $cmd['@http']['collect_stats'] = true;
            $eventData = $this->populateRequestEventData(
                $cmd,
                $request,
                $this->getNewEvent($cmd, $request)
            );
        }

        $g = function ($value) use ($eventData, $enabled) {
            if ($enabled) {
                $eventData = $this->populateResultEventData(
                    $value,
                    $eventData
                );
                $this->sendEventData($eventData);

                if ($value instanceof MonitoringEventsInterface) {
                    $value->appendMonitoringEvent($eventData);
                }
            }
            if ($value instanceof \Exception || $value instanceof \Throwable) {
                return Promise\Create::rejectionFor($value);
            }
            return $value;
        };

        return Promise\Create::promiseFor($handler($cmd, $request))->then($g, $g);
    }

    private function getClientId()
    {
        return $this->unwrappedOptions()->getClientId();
    }

    private function getNewEvent(
        CommandInterface $cmd,
        RequestInterface $request
    ) {
        $event = [
            'Api' => $cmd->getName(),
            'ClientId' => $this->getClientId(),
            'Region' => $this->getRegion(),
            'Service' => $this->getService(),
            'Timestamp' => (int) floor(microtime(true) * 1000),
            'UserAgent' => substr(
                $request->getHeaderLine('User-Agent') . ' ' . \Aws\default_user_agent(),
                0,
                256
            ),
            'Version' => 1
        ];
        return $event;
    }

    private function getHost()
    {
        return $this->unwrappedOptions()->getHost();
    }

    private function getPort()
    {
        return $this->unwrappedOptions()->getPort();
    }

    private function getRegion()
    {
        return $this->region;
    }

    private function getService()
    {
        return $this->service;
    }

    /**
     * Returns enabled flag from options, unwrapping options if necessary.
     *
     * @return bool
     */
    private function isEnabled()
    {
        return $this->unwrappedOptions()->isEnabled();
    }

    /**
     * Returns $eventData array with information from the request and command.
     *
     * @param CommandInterface $cmd
     * @param RequestInterface $request
     * @param array $event
     * @return array
     */
    protected function populateRequestEventData(
        CommandInterface $cmd,
        RequestInterface $request,
        array $event
    ) {
        $dataFormat = static::getRequestData($request);
        foreach ($dataFormat as $eventKey => $value) {
            if ($value !== null) {
                $event[$eventKey] = $value;
            }
        }
        return $event;
    }

    /**
     * Returns $eventData array with information from the response, including
     * the calculation for attempt latency.
     *
     * @param ResultInterface|\Exception $result
     * @param array $event
     * @return array
     */
    protected function populateResultEventData(
        $result,
        array $event
    ) {
        $dataFormat = static::getResponseData($result);
        foreach ($dataFormat as $eventKey => $value) {
            if ($value !== null) {
                $event[$eventKey] = $value;
            }
        }
        return $event;
    }


    /**
     * Checks if the socket is created. If PHP version is greater or equals to 8 then,
     * it will check if the var is instance of \Socket otherwise it will check if is
     * a resource.
     *
     * @return bool Returns true if the socket is created, false otherwise.
     */
    private function isSocketCreated(): bool
    {
        // Before version 8, sockets are resources
        // After version 8, sockets are instances of Socket
        if (PHP_MAJOR_VERSION >= 8) {
            $socketClass = '\Socket';
            return self::$socket instanceof $socketClass;
        } else {
            return is_resource(self::$socket);
        }
    }

    /**
     * Creates a UDP socket resource and stores it with the class, or retrieves
     * it if already instantiated and connected. Handles error-checking and
     * re-connecting if necessary. If $forceNewConnection is set to true, a new
     * socket will be created.
     *
     * @param bool $forceNewConnection
     * @return Resource
     */
    private function prepareSocket($forceNewConnection = false)
    {
        if (!$this->isSocketCreated()
            || $forceNewConnection
            || socket_last_error(self::$socket)
        ) {
            self::$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
            socket_clear_error(self::$socket);
            socket_connect(self::$socket, $this->getHost(), $this->getPort());
        }

        return self::$socket;
    }

    /**
     * Sends formatted monitoring event data via the UDP socket connection to
     * the CSM agent endpoint.
     *
     * @param array $eventData
     * @return int
     */
    private function sendEventData(array $eventData)
    {
        $socket = $this->prepareSocket();
        $datagram = json_encode($eventData);
        $result = socket_write($socket, $datagram, strlen($datagram));
        if ($result === false) {
            $this->prepareSocket(true);
        }
        return $result;
    }

    /**
     * Unwraps options, if needed, and returns them.
     *
     * @return ConfigurationInterface
     */
    private function unwrappedOptions()
    {
        if (!($this->options instanceof ConfigurationInterface)) {
            try {
                $this->options = ConfigurationProvider::unwrap($this->options);
            } catch (\Exception $e) {
                // Errors unwrapping CSM config defaults to disabling it
                $this->options = new Configuration(
                    false,
                    ConfigurationProvider::DEFAULT_HOST,
                    ConfigurationProvider::DEFAULT_PORT
                );
            }
        }
        return $this->options;
    }
}
ConfigurationInterface.php000064400000001503151520663060011710 0ustar00<?php
namespace Aws\ClientSideMonitoring;

/**
 * Provides access to client-side monitoring configuration options:
 * 'client_id', 'enabled', 'host', 'port'
 */
interface ConfigurationInterface
{
    /**
     * Checks whether or not client-side monitoring is enabled.
     *
     * @return bool
     */
    public function isEnabled();

    /**
     * Returns the Client ID, if available.
     *
     * @return string|null
     */
    public function getClientId();

    /**
     * Returns the configured host.
     *
     * @return string|null
     */
    public function getHost();

    /**
     * Returns the configured port.
     *
     * @return int|null
     */
    public function getPort();

    /**
     * Returns the configuration as an associative array.
     *
     * @return array
     */
    public function toArray();
}
MonitoringMiddlewareInterface.php000064400000001431151520663060013224 0ustar00<?php

namespace Aws\ClientSideMonitoring;

use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Aws\ResultInterface;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;

/**
 * @internal
 */
interface MonitoringMiddlewareInterface
{

    /**
     * Data for event properties to be sent to the monitoring agent.
     *
     * @param RequestInterface $request
     * @return array
     */
    public static function getRequestData(RequestInterface $request);


    /**
     * Data for event properties to be sent to the monitoring agent.
     *
     * @param ResultInterface|AwsException|\Exception $klass
     * @return array
     */
    public static function getResponseData($klass);

    public function __invoke(CommandInterface $cmd, RequestInterface $request);
}ConfigurationProvider.php000064400000021131151520663060011601 0ustar00<?php
namespace Aws\ClientSideMonitoring;

use Aws\AbstractConfigurationProvider;
use Aws\CacheInterface;
use Aws\ClientSideMonitoring\Exception\ConfigurationException;
use Aws\ConfigurationProviderInterface;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * A configuration provider is a function that accepts no arguments and returns
 * a promise that is fulfilled with a {@see \Aws\ClientSideMonitoring\ConfigurationInterface}
 * or rejected with an {@see \Aws\ClientSideMonitoring\Exception\ConfigurationException}.
 *
 * <code>
 * use Aws\ClientSideMonitoring\ConfigurationProvider;
 * $provider = ConfigurationProvider::defaultProvider();
 * // Returns a ConfigurationInterface or throws.
 * $config = $provider()->wait();
 * </code>
 *
 * Configuration providers can be composed to create configuration using
 * conditional logic that can create different configurations in different
 * environments. You can compose multiple providers into a single provider using
 * {@see Aws\ClientSideMonitoring\ConfigurationProvider::chain}. This function
 * accepts providers as variadic arguments and returns a new function that will
 * invoke each provider until a successful configuration is returned.
 *
 * <code>
 * // First try an INI file at this location.
 * $a = ConfigurationProvider::ini(null, '/path/to/file.ini');
 * // Then try an INI file at this location.
 * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini');
 * // Then try loading from environment variables.
 * $c = ConfigurationProvider::env();
 * // Combine the three providers together.
 * $composed = ConfigurationProvider::chain($a, $b, $c);
 * // Returns a promise that is fulfilled with a configuration or throws.
 * $promise = $composed();
 * // Wait on the configuration to resolve.
 * $config = $promise->wait();
 * </code>
 */
class ConfigurationProvider extends AbstractConfigurationProvider
    implements ConfigurationProviderInterface
{
    const DEFAULT_CLIENT_ID = '';
    const DEFAULT_ENABLED = false;
    const DEFAULT_HOST = '127.0.0.1';
    const DEFAULT_PORT = 31000;
    const ENV_CLIENT_ID = 'AWS_CSM_CLIENT_ID';
    const ENV_ENABLED = 'AWS_CSM_ENABLED';
    const ENV_HOST = 'AWS_CSM_HOST';
    const ENV_PORT = 'AWS_CSM_PORT';
    const ENV_PROFILE = 'AWS_PROFILE';

    public static $cacheKey = 'aws_cached_csm_config';

    protected static $interfaceClass = ConfigurationInterface::class;
    protected static $exceptionClass = ConfigurationException::class;

    /**
     * Create a default config provider that first checks for environment
     * variables, then checks for a specified profile in the environment-defined
     * config file location (env variable is 'AWS_CONFIG_FILE', file location
     * defaults to ~/.aws/config), then checks for the "default" profile in the
     * environment-defined config file location, and failing those uses a default
     * fallback set of configuration options.
     *
     * This provider is automatically wrapped in a memoize function that caches
     * previously provided config options.
     *
     * @param array $config
     *
     * @return callable
     */
    public static function defaultProvider(array $config = [])
    {
        $configProviders = [self::env()];
        if (
            !isset($config['use_aws_shared_config_files'])
            || $config['use_aws_shared_config_files'] != false
        ) {
            $configProviders[] = self::ini();
        }
        $configProviders[] = self::fallback();

        $memo = self::memoize(
            call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)
        );

        if (isset($config['csm']) && $config['csm'] instanceof CacheInterface) {
            return self::cache($memo, $config['csm'], self::$cacheKey);
        }

        return $memo;
    }

    /**
     * Provider that creates CSM config from environment variables.
     *
     * @return callable
     */
    public static function env()
    {
        return function () {
            // Use credentials from environment variables, if available
            $enabled = getenv(self::ENV_ENABLED);
            if ($enabled !== false) {
                return Promise\Create::promiseFor(
                    new Configuration(
                        $enabled,
                        getenv(self::ENV_HOST) ?: self::DEFAULT_HOST,
                        getenv(self::ENV_PORT) ?: self::DEFAULT_PORT,
                        getenv(self:: ENV_CLIENT_ID) ?: self::DEFAULT_CLIENT_ID
                     )
                );
            }

            return self::reject('Could not find environment variable CSM config'
                . ' in ' . self::ENV_ENABLED. '/' . self::ENV_HOST . '/'
                . self::ENV_PORT . '/' . self::ENV_CLIENT_ID);
        };
    }

    /**
     * Fallback config options when other sources are not set.
     *
     * @return callable
     */
    public static function fallback()
    {
        return function() {
            return Promise\Create::promiseFor(
                new Configuration(
                    self::DEFAULT_ENABLED,
                    self::DEFAULT_HOST,
                    self::DEFAULT_PORT,
                    self::DEFAULT_CLIENT_ID
                )
            );
        };
    }

    /**
     * Config provider that creates config using a config file whose location
     * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
     * ~/.aws/config if not specified
     *
     * @param string|null $profile  Profile to use. If not specified will use
     *                              the "default" profile.
     * @param string|null $filename If provided, uses a custom filename rather
     *                              than looking in the default directory.
     *
     * @return callable
     */
    public static function ini($profile = null, $filename = null)
    {
        $filename = $filename ?: (self::getDefaultConfigFilename());
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'aws_csm');

        return function () use ($profile, $filename) {
            if (!@is_readable($filename)) {
                return self::reject("Cannot read CSM config from $filename");
            }
            $data = \Aws\parse_ini_file($filename, true);
            if ($data === false) {
                return self::reject("Invalid config file: $filename");
            }
            if (!isset($data[$profile])) {
                return self::reject("'$profile' not found in config file");
            }
            if (!isset($data[$profile]['csm_enabled'])) {
                return self::reject("Required CSM config values not present in
                    INI profile '{$profile}' ({$filename})");
            }

            // host is optional
            if (empty($data[$profile]['csm_host'])) {
                $data[$profile]['csm_host'] = self::DEFAULT_HOST;
            }

            // port is optional
            if (empty($data[$profile]['csm_port'])) {
                $data[$profile]['csm_port'] = self::DEFAULT_PORT;
            }

            // client_id is optional
            if (empty($data[$profile]['csm_client_id'])) {
                $data[$profile]['csm_client_id'] = self::DEFAULT_CLIENT_ID;
            }

            return Promise\Create::promiseFor(
                new Configuration(
                    $data[$profile]['csm_enabled'],
                    $data[$profile]['csm_host'],
                    $data[$profile]['csm_port'],
                    $data[$profile]['csm_client_id']
                )
            );
        };
    }

    /**
     * Unwraps a configuration object in whatever valid form it is in,
     * always returning a ConfigurationInterface object.
     *
     * @param  mixed $config
     * @return ConfigurationInterface
     * @throws \InvalidArgumentException
     */
    public static function unwrap($config)
    {
        if (is_callable($config)) {
            $config = $config();
        }
        if ($config instanceof PromiseInterface) {
            $config = $config->wait();
        }
        if ($config instanceof ConfigurationInterface) {
            return $config;
        } elseif (is_array($config) && isset($config['enabled'])) {
            $client_id = isset($config['client_id']) ? $config['client_id']
                : self::DEFAULT_CLIENT_ID;
            $host = isset($config['host']) ? $config['host']
                : self::DEFAULT_HOST;
            $port = isset($config['port']) ? $config['port']
                : self::DEFAULT_PORT;
            return new Configuration($config['enabled'], $host, $port, $client_id);
        }

        throw new \InvalidArgumentException('Not a valid CSM configuration '
            . 'argument.');
    }
}
Configuration.php000064400000003161151520663060010071 0ustar00<?php
namespace Aws\ClientSideMonitoring;

class Configuration implements ConfigurationInterface
{
    private $clientId;
    private $enabled;
    private $host;
    private $port;

    /**
     * Constructs a new Configuration object with the specified CSM options set.
     *
     * @param mixed $enabled
     * @param string $host
     * @param string|int $port
     * @param string $clientId
     */
    public function __construct($enabled, $host, $port, $clientId = '')
    {
        $this->host = $host;
        $this->port = filter_var($port, FILTER_VALIDATE_INT);
        if ($this->port === false) {
            throw new \InvalidArgumentException(
                "CSM 'port' value must be an integer!");
        }

        // Unparsable $enabled flag errors on the side of disabling CSM
        $this->enabled = filter_var($enabled, FILTER_VALIDATE_BOOLEAN);
        $this->clientId = trim($clientId);
    }

    /**
     * {@inheritdoc}
     */
    public function isEnabled()
    {
        return $this->enabled;
    }

    /**
     * {@inheritdoc}
     */
    public function getClientId()
    {
        return $this->clientId;
    }

    /**
     * /{@inheritdoc}
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * {@inheritdoc}
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        return [
            'client_id' => $this->getClientId(),
            'enabled' => $this->isEnabled(),
            'host' => $this->getHost(),
            'port' => $this->getPort()
        ];
    }
}Exception/ConfigurationException.php000064400000000534151520663060013707 0ustar00<?php
namespace Aws\ClientSideMonitoring\Exception;

use Aws\HasMonitoringEventsTrait;
use Aws\MonitoringEventsInterface;


/**
 * Represents an error interacting with configuration for client-side monitoring.
 */
class ConfigurationException extends \RuntimeException implements
    MonitoringEventsInterface
{
    use HasMonitoringEventsTrait;
}
ApiCallAttemptMonitoringMiddleware.php000064400000020327151520663060014175 0ustar00<?php

namespace Aws\ClientSideMonitoring;

use Aws\CommandInterface;
use Aws\Credentials\CredentialsInterface;
use Aws\Exception\AwsException;
use Aws\ResponseContainerInterface;
use Aws\ResultInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal
 */
class ApiCallAttemptMonitoringMiddleware extends AbstractMonitoringMiddleware
{

    /**
     * Standard middleware wrapper function with CSM options passed in.
     *
     * @param callable $credentialProvider
     * @param mixed  $options
     * @param string $region
     * @param string $service
     * @return callable
     */
    public static function wrap(
        callable $credentialProvider,
        $options,
        $region,
        $service
    ) {
        return function (callable $handler) use (
            $credentialProvider,
            $options,
            $region,
            $service
        ) {
            return new static(
                $handler,
                $credentialProvider,
                $options,
                $region,
                $service
            );
        };
    }

    /**
     * {@inheritdoc}
     */
    public static function getRequestData(RequestInterface $request)
    {
        return [
            'Fqdn' => $request->getUri()->getHost(),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function getResponseData($klass)
    {
        if ($klass instanceof ResultInterface) {
            return [
                'AttemptLatency' => self::getResultAttemptLatency($klass),
                'DestinationIp' => self::getResultDestinationIp($klass),
                'DnsLatency' => self::getResultDnsLatency($klass),
                'HttpStatusCode' => self::getResultHttpStatusCode($klass),
                'XAmzId2' => self::getResultHeader($klass, 'x-amz-id-2'),
                'XAmzRequestId' => self::getResultHeader($klass, 'x-amz-request-id'),
                'XAmznRequestId' => self::getResultHeader($klass, 'x-amzn-RequestId'),
            ];
        }
        if ($klass instanceof AwsException) {
            return [
                'AttemptLatency' => self::getAwsExceptionAttemptLatency($klass),
                'AwsException' => substr(
                    self::getAwsExceptionErrorCode($klass),
                    0,
                    128
                ),
                'AwsExceptionMessage' => substr(
                    self::getAwsExceptionMessage($klass),
                    0,
                    512
                ),
                'DestinationIp' => self::getAwsExceptionDestinationIp($klass),
                'DnsLatency' => self::getAwsExceptionDnsLatency($klass),
                'HttpStatusCode' => self::getAwsExceptionHttpStatusCode($klass),
                'XAmzId2' => self::getAwsExceptionHeader($klass, 'x-amz-id-2'),
                'XAmzRequestId' => self::getAwsExceptionHeader(
                    $klass,
                    'x-amz-request-id'
                ),
                'XAmznRequestId' => self::getAwsExceptionHeader(
                    $klass,
                    'x-amzn-RequestId'
                ),
            ];
        }
        if ($klass instanceof \Exception) {
            return [
                'HttpStatusCode' => self::getExceptionHttpStatusCode($klass),
                'SdkException' => substr(
                    self::getExceptionCode($klass),
                    0,
                    128
                ),
                'SdkExceptionMessage' => substr(
                    self::getExceptionMessage($klass),
                    0,
                    512
                ),
                'XAmzId2' => self::getExceptionHeader($klass, 'x-amz-id-2'),
                'XAmzRequestId' => self::getExceptionHeader($klass, 'x-amz-request-id'),
                'XAmznRequestId' => self::getExceptionHeader($klass, 'x-amzn-RequestId'),
            ];
        }

        throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface, AwsException or Exception.');
    }

    private static function getResultAttemptLatency(ResultInterface $result)
    {
        if (isset($result['@metadata']['transferStats']['http'])) {
            $attempt = end($result['@metadata']['transferStats']['http']);
            if (isset($attempt['total_time'])) {
                return (int) floor($attempt['total_time'] * 1000);
            }
        }
        return null;
    }

    private static function getResultDestinationIp(ResultInterface $result)
    {
        if (isset($result['@metadata']['transferStats']['http'])) {
            $attempt = end($result['@metadata']['transferStats']['http']);
            if (isset($attempt['primary_ip'])) {
                return $attempt['primary_ip'];
            }
        }
        return null;
    }

    private static function getResultDnsLatency(ResultInterface $result)
    {
        if (isset($result['@metadata']['transferStats']['http'])) {
            $attempt = end($result['@metadata']['transferStats']['http']);
            if (isset($attempt['namelookup_time'])) {
                return (int) floor($attempt['namelookup_time'] * 1000);
            }
        }
        return null;
    }

    private static function getResultHttpStatusCode(ResultInterface $result)
    {
        return $result['@metadata']['statusCode'];
    }

    private static function getAwsExceptionAttemptLatency(AwsException $e) {
        $attempt = $e->getTransferInfo();
        if (isset($attempt['total_time'])) {
            return (int) floor($attempt['total_time'] * 1000);
        }
        return null;
    }

    private static function getAwsExceptionErrorCode(AwsException $e) {
        return $e->getAwsErrorCode();
    }

    private static function getAwsExceptionMessage(AwsException $e) {
        return $e->getAwsErrorMessage();
    }

    private static function getAwsExceptionDestinationIp(AwsException $e) {
        $attempt = $e->getTransferInfo();
        if (isset($attempt['primary_ip'])) {
            return $attempt['primary_ip'];
        }
        return null;
    }

    private static function getAwsExceptionDnsLatency(AwsException $e) {
        $attempt = $e->getTransferInfo();
        if (isset($attempt['namelookup_time'])) {
            return (int) floor($attempt['namelookup_time'] * 1000);
        }
        return null;
    }

    private static function getAwsExceptionHttpStatusCode(AwsException $e) {
        $response = $e->getResponse();
        if ($response !== null) {
            return $response->getStatusCode();
        }
        return null;
    }

    private static function getExceptionHttpStatusCode(\Exception $e) {
        if ($e instanceof ResponseContainerInterface) {
            $response = $e->getResponse();
            if ($response instanceof ResponseInterface) {
                return $response->getStatusCode();
            }
        }
        return null;
    }

    private static function getExceptionCode(\Exception $e) {
        if (!($e instanceof AwsException)) {
            return get_class($e);
        }
        return null;
    }

    private static function getExceptionMessage(\Exception $e) {
        if (!($e instanceof AwsException)) {
            return $e->getMessage();
        }
        return null;
    }

    /**
     * {@inheritdoc}
     */
    protected function populateRequestEventData(
        CommandInterface $cmd,
        RequestInterface $request,
        array $event
    ) {
        $event = parent::populateRequestEventData($cmd, $request, $event);
        $event['Type'] = 'ApiCallAttempt';
        return $event;
    }

    /**
     * {@inheritdoc}
     */
    protected function populateResultEventData(
        $result,
        array $event
    ) {
        $event = parent::populateResultEventData($result, $event);

        $provider = $this->credentialProvider;
        /** @var CredentialsInterface $credentials */
        $credentials = $provider()->wait();
        $event['AccessKey'] = $credentials->getAccessKeyId();
        $sessionToken = $credentials->getSecurityToken();
        if ($sessionToken !== null) {
            $event['SessionToken'] = $sessionToken;
        }
        if (empty($event['AttemptLatency'])) {
            $event['AttemptLatency'] = (int) (floor(microtime(true) * 1000) - $event['Timestamp']);
        }
        return $event;
    }
}