Repository: pusher/pusher-http-php (php)

Issue:
Log Level is not defined with Monolog
We use Monolog for my Logger implementation in my app. When I set the logger in Pusher client we obtain this error:

"Exception: Level "Pusher: INFO: create_curl( https://api-eu.pusher.com:443/apps/*****************&auth_version=1.0&body_md5=************* )" is not defined, use one of: 100, 200, 250, 300, 400, 500, 550, 600 [] []"

Monolog implements Psr `LoggerInterface`  [1] with next `log` method:

```php
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array());
```
[1] https://github.com/php-fig/log/blob/master/Psr/Log/LoggerInterface.php

But Pusher client use incorrectly `log` method, without `$level` parameter.

```php
    /**
     * Set a logger to be informed of internal log messages.
     *
     * @return void
     */
    public function set_logger($logger)
    {
        $this->logger = $logger;
    }
    /**
     * Log a string.
     *
     * @param string $msg The message to log
     *
     * @return void
     */
    private function log($msg)
    {
        if (is_null($this->logger) === false) {
            $this->logger->log('Pusher: '.$msg);
        }
    }
```


Discussion:
A quick and shitty solution is write a wrapper for encapsulate the `Psr\Log\LoggerInterface`.

```php
use Psr\Log\LoggerInterface;

class PsrToPusherLogger {
    protected $defaultLevel;
    protected $logger;

    public function __construct(LoggerInterface $logger, $defaultLevel = 100) {
        $this->logger = $logger;
        $this->defaultLevel = $defaultLevel;
    }

    public function log($message) {
        $this->logger->log($this->defaultLevel, $message);
    }
}
```

For use this solution:

```php
use Monolog\Logger;

// ...

$pusher->set_logger(new PsrToPusherLogger($logger, Logger::INFO));
```

We don't implement the logger in this fashion initially, however now that PSR-3-logger entered into the accepted state, it is probably time that we did. I'll look into modifying this library to officially support this logging interface. Thanks for the report!

Patch:
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -345,12 +345,23 @@ $pusher->notify(array("test"), $data);
 
 ## Debugging & Logging
 
-The best way to debug your applications interaction with server is to set a logger
-for the library so you can see the internal workings within the library and interactions
-with the Pusher service.
+The best way to debug your applications interaction with server is to set a logger for the library so you can see the internal workings within the library and interactions with the Pusher service.
 
-You set up logging by passing an object with a `log` function to the `pusher->set_logger`
-function:
+### PSR-3 Support
+
+The recommended approach of logging is to use a [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) compliant logger implementing `Psr\Log\LoggerInterface`. The `Pusher` object implements `Psr\Log\LoggerAwareInterface`, meaning you call `setLogger(LoggerInterface $logger)` to set the logger instance.
+
+```php
+// where $logger implements `LoggerInterface`
+
+$pusher->setLogger($logger);
+```
+
+### Custom Logger (deprecated)
+
+> **Warning**: Using `Pusher::set_logger()` and a custom object implementing `log()` is now deprecated and will be removed in the future. Please use a PSR-3 compliant logger.
+
+You set up logging by passing an object with a `log` function to the `pusher->set_logger` function:
 
 ```php
 class MyLogger {
@@ -362,9 +373,7 @@ class MyLogger {
 $pusher->set_logger( new MyLogger() );
 ```
 
-If you use the above example in code executed from the console/terminal the debug
-information will be output there. If you use this within a web app then the output
-will appear within the generated app output e.g. HTML.
+If you use the above example in code executed from the console/terminal the debug information will be output there. If you use this within a web app then the output will appear within the generated app output e.g. HTML.
 
 ## Running the tests
 
diff --git a/composer.json b/composer.json
--- a/composer.json
+++ b/composer.json
@@ -5,7 +5,8 @@
     "license": "MIT",
     "require": {
         "php": "^5.4 || ^7.0",
-        "ext-curl": "*"
+        "ext-curl": "*",
+        "psr/log": "^1.0"
     },
     "require-dev": {
         "phpunit/phpunit": "^4.8 || ^5.7"
diff --git a/src/Pusher.php b/src/Pusher.php
--- a/src/Pusher.php
+++ b/src/Pusher.php
@@ -2,8 +2,15 @@
 
 namespace Pusher;
 
-class Pusher
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+class Pusher implements LoggerAwareInterface
 {
+    use LoggerAwareTrait;
+
     public static $VERSION = '3.0.0';
 
     private $settings = array(
@@ -13,7 +20,6 @@ class Pusher
         'debug'        => false,
         'curl_options' => array(),
     );
-    private $logger = null;
     private $ch = null; // Curl handler
 
     /**
@@ -62,8 +68,10 @@ public function __construct($auth_key, $secret, $app_id, $options = array(), $ho
 
             $this->settings['host'] = $host;
 
-            $this->log('INFO: Legacy $host parameter provided: '.
-                $this->settings['scheme'].' host: '.$this->settings['host']);
+            $this->log('Legacy $host parameter provided: {scheme} host: {host}', array(
+                'scheme' => $this->settings['scheme'],
+                'host'   => $this->settings['host'],
+            ));
         }
 
         if (!is_null($port)) {
@@ -141,6 +149,8 @@ public function getSettings()
     /**
      * Set a logger to be informed of internal log messages.
      *
+     * @deprecated Use the PSR-3 compliant Pusher::setLogger() instead. This method will be removed in the next breaking release.
+     *
      * @return void
      */
     public function set_logger($logger)
@@ -155,11 +165,27 @@ public function set_logger($logger)
      *
      * @return void
      */
-    private function log($msg)
+    private function log($msg, array $context = array(), $level = LogLevel::INFO)
     {
-        if (is_null($this->logger) === false) {
-            $this->logger->log('Pusher: '.$msg);
+        if (is_null($this->logger)) {
+            return;
+        }
+
+        if ($this->logger instanceof LoggerInterface) {
+            $this->logger->log($level, $msg, $context);
+
+            return;
         }
+
+        // Support old style logger (deprecated)
+        $msg = sprintf('Pusher: %s: %s', strtoupper($level), $msg);
+        $replacement = array();
+
+        foreach ($context as $k => $v) {
+            $replacement['{'.$k.'}'] = $v;
+        }
+
+        $this->logger->log(strtr($msg, $replacement));
     }
 
     /**
@@ -250,7 +276,7 @@ private function create_curl($domain, $s_url, $request_method = 'GET', $query_pa
 
         $full_url = $domain.$s_url.'?'.$signed_query;
 
-        $this->log('INFO: create_curl( '.$full_url.' )');
+        $this->log('create_curl( {full_url} )', array('full_url' => $full_url));
 
         // Create or reuse existing curl handle
         if (null === $this->ch) {
@@ -304,10 +330,10 @@ private function exec_curl($ch)
         $response['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 
         if ($response['body'] === false || $response['status'] < 200 || 400 <= $response['status']) {
-            $this->log('ERROR: exec_curl error: '.curl_error($ch));
+            $this->log('exec_curl error: {error}', array('error' => curl_error($ch)), LogLevel::ERROR);
         }
 
-        $this->log('INFO: exec_curl response: '.print_r($response, true));
+        $this->log('exec_curl response: {response}', array('response' => print_r($response, true)));
 
         return $response;
     }
@@ -425,7 +451,9 @@ public function trigger($channels, $event, $data, $socket_id = null, $debug = fa
 
         // json_encode might return false on failure
         if (!$data_encoded) {
-            $this->Log('ERROR: Failed to perform json_encode on the the provided data: '.print_r($data, true));
+            $this->log('Failed to perform json_encode on the the provided data: {error}', array(
+                'error' => print_r($data, true),
+            ), LogLevel::ERROR);
         }
 
         $post_params = array();
@@ -443,7 +471,7 @@ public function trigger($channels, $event, $data, $socket_id = null, $debug = fa
 
         $ch = $this->create_curl($this->ddn_domain(), $s_url, 'POST', $query_params);
 
-        $this->log('INFO: trigger POST: '.$post_value);
+        $this->log('trigger POST: {post_value}', compact('post_value'));
 
         curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
 
@@ -490,7 +518,7 @@ public function triggerBatch($batch = array(), $debug = false, $already_encoded
 
         $ch = $this->create_curl($this->ddn_domain(), $s_url, 'POST', $query_params);
 
-        $this->log('INFO: trigger POST: '.$post_value);
+        $this->log('trigger POST: {post_value}', compact('post_value'));
 
         curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
 
@@ -636,7 +664,7 @@ public function notify($interests, $data = array(), $debug = false)
         $query_params = array();
 
         if (is_string($interests)) {
-            $this->log('INFO: ->notify received string interests "'.$interests.'". Converting to array.');
+            $this->log('->notify received string interests "{interests}" Converting to array.', compact('interests'));
             $interests = array($interests);
         }
 
@@ -653,7 +681,7 @@ public function notify($interests, $data = array(), $debug = false)
         $notification_path = '/server_api/v1'.$this->settings['base_path'].'/notifications';
         $ch = $this->create_curl($this->notification_domain(), $notification_path, 'POST', $query_params);
 
-        $this->log('INFO: trigger POST (Native notifications): '.$post_value);
+        $this->log('trigger POST (Native notifications): {post_value}', compact('post_value'));
 
         curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
 


Test patch:
diff --git a/tests/TestLogger.php b/tests/TestLogger.php
--- a/tests/TestLogger.php
+++ b/tests/TestLogger.php
@@ -1,9 +1,21 @@
 <?php
 
-class TestLogger
+use Psr\Log\AbstractLogger;
+
+class TestLogger extends AbstractLogger
 {
-    public function log($msg)
+    /**
+     * {@inheritdoc}
+     */
+    public function log($level, $message, array $context = array())
     {
-        print_r("\n".$msg);
+        $msg = sprintf('Pusher: %s: %s', strtoupper($level), $msg);
+        $replacement = array();
+
+        foreach ($context as $k => $v) {
+            $replacement['{'.$k.'}'] = $v;
+        }
+
+        print_r("\n".strtr($msg, $replacement));
     }
 }
diff --git a/tests/acceptance/channelQueryTest.php b/tests/acceptance/channelQueryTest.php
--- a/tests/acceptance/channelQueryTest.php
+++ b/tests/acceptance/channelQueryTest.php
@@ -5,7 +5,7 @@ class PusherChannelQueryTest extends PHPUnit_Framework_TestCase
     protected function setUp()
     {
         $this->pusher = new Pusher\Pusher(PUSHERAPP_AUTHKEY, PUSHERAPP_SECRET, PUSHERAPP_APPID, true, PUSHERAPP_HOST);
-        $this->pusher->set_logger(new TestLogger());
+        $this->pusher->setLogger(new TestLogger());
     }
 
     public function testChannelInfo()
diff --git a/tests/acceptance/triggerBatchTest.php b/tests/acceptance/triggerBatchTest.php
--- a/tests/acceptance/triggerBatchTest.php
+++ b/tests/acceptance/triggerBatchTest.php
@@ -10,7 +10,7 @@ protected function setUp()
             PUSHERAPP_APPID keys.');
         } else {
             $this->pusher = new Pusher\Pusher(PUSHERAPP_AUTHKEY, PUSHERAPP_SECRET, PUSHERAPP_APPID, true, PUSHERAPP_HOST);
-            $this->pusher->set_logger(new TestLogger());
+            $this->pusher->setLogger(new TestLogger());
         }
     }
 
@@ -34,7 +34,7 @@ public function testEncryptedPush()
             'host'      => PUSHERAPP_HOST,
         );
         $pusher = new Pusher\Pusher(PUSHERAPP_AUTHKEY, PUSHERAPP_SECRET, PUSHERAPP_APPID, $options);
-        $pusher->set_logger(new TestLogger());
+        $pusher->setLogger(new TestLogger());
 
         $batch = array();
         $batch[] = array('channel' => 'test_channel', 'name' => 'my_event', 'data' => array('my' => 'data'));
diff --git a/tests/acceptance/triggerTest.php b/tests/acceptance/triggerTest.php
--- a/tests/acceptance/triggerTest.php
+++ b/tests/acceptance/triggerTest.php
@@ -10,7 +10,7 @@ protected function setUp()
             PUSHERAPP_APPID keys.');
         } else {
             $this->pusher = new Pusher\Pusher(PUSHERAPP_AUTHKEY, PUSHERAPP_SECRET, PUSHERAPP_APPID, true, PUSHERAPP_HOST);
-            $this->pusher->set_logger(new TestLogger());
+            $this->pusher->setLogger(new TestLogger());
         }
     }
 
@@ -38,7 +38,7 @@ public function testEncryptedPush()
             'host'      => PUSHERAPP_HOST,
         );
         $pusher = new Pusher\Pusher(PUSHERAPP_AUTHKEY, PUSHERAPP_SECRET, PUSHERAPP_APPID, $options);
-        $pusher->set_logger(new TestLogger());
+        $pusher->setLogger(new TestLogger());
 
         $structure_trigger = $pusher->trigger('test_channel', 'my_event', array('encrypted' => 1));
         $this->assertTrue($structure_trigger, 'Trigger with over encrypted connection');