diff --git a/composer.json b/composer.json index abacbe84b8..94297b05c8 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "license": "Apache-2.0", "require": { "php": "^8.0", - "google/auth": "^1.37", + "google/auth": "dev-http-logging as 1.3", "google/apiclient-services": "~0.350", "firebase/php-jwt": "^6.0", "monolog/monolog": "^2.9||^3.0", @@ -21,7 +21,7 @@ "symfony/css-selector": "~2.1", "cache/filesystem-adapter": "^1.1", "phpcompatibility/php-compatibility": "^9.2", - "composer/composer": "^1.10.23", + "composer/composer": "^2.8", "phpspec/prophecy-phpunit": "^2.1", "phpunit/phpunit": "^9.6" }, diff --git a/src/AuthHandler/Guzzle6AuthHandler.php b/src/AuthHandler/Guzzle6AuthHandler.php index 7e8a815c29..e0876509f2 100644 --- a/src/AuthHandler/Guzzle6AuthHandler.php +++ b/src/AuthHandler/Guzzle6AuthHandler.php @@ -11,6 +11,7 @@ use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerInterface; /** * This supports Guzzle 6 @@ -46,13 +47,14 @@ public function attachCredentials( public function attachCredentialsCache( ClientInterface $http, FetchAuthTokenCache $credentials, - callable $tokenCallback = null + callable $tokenCallback = null, + false|null|LoggerInterface $logger = null ) { // if we end up needing to make an HTTP request to retrieve credentials, we // can use our existing one, but we need to throw exceptions so the error // bubbles up. $authHttp = $this->createAuthHttp($http); - $authHttpHandler = HttpHandlerFactory::build($authHttp); + $authHttpHandler = HttpHandlerFactory::build($authHttp, $logger); $middleware = new AuthTokenMiddleware( $credentials, $authHttpHandler, diff --git a/src/Client.php b/src/Client.php index 1285c49a7a..78a4b29238 100644 --- a/src/Client.php +++ b/src/Client.php @@ -172,7 +172,10 @@ class Client * @type string $universe_domain * Setting the universe domain will change the default rootUrl of the service. * If not set explicitly, the universe domain will be the value provided in the - *. "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com". + * "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com". + * @type false|LoggerInterface $logger + * A PSR-3 compliant logger. If set to false logging is disabled ignoring the + * 'GOOGLE_SDK_DEBUG_LOGGING' flag. * } */ public function __construct(array $config = []) @@ -480,7 +483,8 @@ public function authorize(ClientInterface $http = null) return $authHandler->attachCredentialsCache( $http, $credentials, - $this->config['token_callback'] + $this->config['token_callback'], + $this->logger ); } @@ -969,7 +973,8 @@ public function execute(RequestInterface $request, $expectedClass = null) $request, $expectedClass, $this->config['retry'], - $this->config['retry_map'] + $this->config['retry_map'], + $this->logger ); } @@ -1161,9 +1166,9 @@ public function setCacheConfig(array $cacheConfig) /** * Set the Logger object - * @param LoggerInterface $logger + * @param false|LoggerInterface $logger */ - public function setLogger(LoggerInterface $logger) + public function setLogger(false|LoggerInterface $logger) { $this->logger = $logger; } @@ -1173,7 +1178,7 @@ public function setLogger(LoggerInterface $logger) */ public function getLogger() { - if (!isset($this->logger)) { + if (!$this->logger) { $this->logger = $this->createDefaultLogger(); } @@ -1182,13 +1187,18 @@ public function getLogger() protected function createDefaultLogger() { - $logger = new Logger('google-api-php-client'); - if ($this->isAppEngine()) { - $handler = new MonologSyslogHandler('app', LOG_USER, Logger::NOTICE); - } else { - $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE); + $logger = $this->logger ?? ApplicationDefaultCredentials::getDefaultLogger(); + + // If the logger is explicitely set to false, we return our NOOP code to avoid breaking changes + if ($logger === false || $logger === null) { + $logger = new Logger('google-api-php-client'); + if ($this->isAppEngine()) { + $handler = new MonologSyslogHandler('app', LOG_USER, Logger::NOTICE); + } else { + $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE); + } + $logger->pushHandler($handler); } - $logger->pushHandler($handler); return $logger; } @@ -1296,7 +1306,8 @@ private function createApplicationDefaultCredentials() null, $sub ? null : $this->config['cache_config'], $sub ? null : $this->getCache(), - $this->config['quota_project'] + $this->config['quota_project'], + logger: $this->logger ); } diff --git a/src/Http/REST.php b/src/Http/REST.php index 70e48e4b86..5850e19945 100644 --- a/src/Http/REST.php +++ b/src/Http/REST.php @@ -25,6 +25,7 @@ use GuzzleHttp\Psr7\Response; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; /** * This class implements the RESTful transport of apiServiceRequest()'s @@ -41,6 +42,7 @@ class REST * @param class-string|false|null $expectedClass * @param array $config * @param array $retryMap + * @param false|null|LoggerInterface $logger * @return mixed|T|null * @throws \Google\Service\Exception on server side error (ie: not authenticated, * invalid or malformed post body, invalid url) @@ -50,13 +52,14 @@ public static function execute( RequestInterface $request, $expectedClass = null, $config = [], - $retryMap = null + $retryMap = null, + LoggerInterface $logger = null, ) { $runner = new Runner( $config, sprintf('%s %s', $request->getMethod(), (string) $request->getUri()), [self::class, 'doExecute'], - [$client, $request, $expectedClass] + [$client, $request, $expectedClass, $logger] ); if (null !== $retryMap) { @@ -73,14 +76,15 @@ public static function execute( * @param ClientInterface $client * @param RequestInterface $request * @param class-string|false|null $expectedClass + * @param false|null|LoggerInterface $logger * @return mixed|T|null * @throws \Google\Service\Exception on server side error (ie: not authenticated, * invalid or malformed post body, invalid url) */ - public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null) + public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null, LoggerInterface $logger = null) { try { - $httpHandler = HttpHandlerFactory::build($client); + $httpHandler = HttpHandlerFactory::build($client, $logger); $response = $httpHandler($request); } catch (RequestException $e) { // if Guzzle throws an exception, catch it and handle the response diff --git a/tests/Google/ClientTest.php b/tests/Google/ClientTest.php index 4e53e34b43..49d7bacf12 100644 --- a/tests/Google/ClientTest.php +++ b/tests/Google/ClientTest.php @@ -26,15 +26,18 @@ use Google\Auth\FetchAuthTokenCache; use Google\Auth\CredentialsLoader; use Google\Auth\GCECache; +use Google\Auth\Logging\StdOutLogger; use Google\Auth\Credentials\GCECredentials; use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Exception\ClientException; +use Monolog\Logger; use Prophecy\Argument; use Psr\Http\Message\RequestInterface; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LogLevel; use ReflectionClass; use ReflectionMethod; use InvalidArgumentException; @@ -228,12 +231,16 @@ public function testPrepareService() $stream = $this->prophesize('GuzzleHttp\Psr7\Stream'); $stream->__toString()->willReturn(''); + $stream->getContents()->willReturn(''); $response = $this->prophesize('Psr\Http\Message\ResponseInterface'); $response->getBody() - ->shouldBeCalledTimes(1) + ->shouldBeCalledTimes(2) ->willReturn($stream->reveal()); + $response->getHeaders() + ->willReturn([]); + $response->getStatusCode()->willReturn(200); $http = $this->prophesize('GuzzleHttp\ClientInterface'); @@ -957,4 +964,118 @@ public function testUniverseDomainMismatch() ]); $client->authorize(); } + + public function testPassAFalseLoggerWillNotLog() + { + putenv('GOOGLE_SDK_DEBUG_LOGGING=true'); + $client = new Client([ + 'logger' => false + ]); + + $stream = $this->prophesize('GuzzleHttp\Psr7\Stream'); + $stream->__toString()->willReturn(''); + $stream->getContents()->willReturn(''); + + $response = $this->prophesize('Psr\Http\Message\ResponseInterface'); + $response->getBody() + ->shouldBeCalledTimes(2) + ->willReturn($stream->reveal()); + + $response->getHeaders() + ->willReturn([]); + + $response->getStatusCode()->willReturn(200); + + $http = $this->prophesize('GuzzleHttp\ClientInterface'); + + $http->send(Argument::type('Psr\Http\Message\RequestInterface'), []) + ->shouldBeCalledTimes(1) + ->willReturn($response->reveal()); + + ob_start(); + + $client->setHttpClient($http->reveal()); + $dr_service = new Drive($client); + $dr_service->files->listFiles(); + + $buffer = ob_get_clean(); + $this->assertEquals($buffer, ''); + putenv('GOOGLE_SDK_DEBUG_LOGGING'); + } + + public function testFlagActivatesLogging() + { + putenv('GOOGLE_SDK_DEBUG_LOGGING=true'); + $client = new Client([]); + + $stream = $this->prophesize('GuzzleHttp\Psr7\Stream'); + $stream->__toString()->willReturn(''); + $stream->getContents()->willReturn(''); + + $response = $this->prophesize('Psr\Http\Message\ResponseInterface'); + $response->getBody() + ->shouldBeCalledTimes(2) + ->willReturn($stream->reveal()); + + $response->getHeaders() + ->willReturn([]); + + $response->getStatusCode()->willReturn(200); + + $http = $this->prophesize('GuzzleHttp\ClientInterface'); + + $http->send(Argument::type('Psr\Http\Message\RequestInterface'), []) + ->shouldBeCalledTimes(1) + ->willReturn($response->reveal()); + + ob_start(); + + $client->setHttpClient($http->reveal()); + $dr_service = new Drive($client); + $dr_service->files->listFiles(); + + $buffer = ob_get_clean(); + + $this->assertNotEquals('', $buffer); + putenv('GOOGLE_SDK_DEBUG_LOGGING'); + } + + public function testPassLoggerEnablesLogging() + { + putenv('GOOGLE_SDK_DEBUG_LOGGING=true'); + $client = new Client([ + 'logger' => new StdOutLogger(LogLevel::INFO), + ]); + + $stream = $this->prophesize('GuzzleHttp\Psr7\Stream'); + $stream->__toString()->willReturn(''); + $stream->getContents()->willReturn(''); + + $response = $this->prophesize('Psr\Http\Message\ResponseInterface'); + $response->getBody() + ->shouldBeCalledTimes(2) + ->willReturn($stream->reveal()); + + $response->getHeaders() + ->willReturn([]); + + $response->getStatusCode()->willReturn(200); + + $http = $this->prophesize('GuzzleHttp\ClientInterface'); + + $http->send(Argument::type('Psr\Http\Message\RequestInterface'), []) + ->shouldBeCalledTimes(1) + ->willReturn($response->reveal()); + + ob_start(); + + $client->setHttpClient($http->reveal()); + $dr_service = new Drive($client); + $dr_service->files->listFiles(); + + $buffer = ob_get_clean(); + + $this->assertNotEquals('', $buffer); + putenv('GOOGLE_SDK_DEBUG_LOGGING'); + } } diff --git a/tests/Google/Service/ResourceTest.php b/tests/Google/Service/ResourceTest.php index ccf29a7f76..243e56809b 100644 --- a/tests/Google/Service/ResourceTest.php +++ b/tests/Google/Service/ResourceTest.php @@ -393,6 +393,8 @@ public function testSuccessResponseWithVeryLongBody() $stream = $this->prophesize(Stream::class); $stream->__toString() ->shouldNotBeCalled(); + $stream->getContents() + ->shouldBeCalledtimes(1); $response = new Response(200, [], $stream->reveal()); $http = $this->prophesize("GuzzleHttp\Client");