diff --git a/README.md b/README.md index bf3365d4..83ff9164 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Features](#features) - [Schema](#schema) - [Purge](#purge) + - [Logs](#logs) - [Contributing / Testing](#contributing--testing) ## Installation @@ -214,6 +215,7 @@ use Parse\ParseCloud; use Parse\ParseClient; use Parse\ParsePushStatus; use Parse\ParseServerInfo; +use Parse\ParseLogs; ``` ### Parse Objects @@ -575,6 +577,25 @@ Only do this if you _really_ need to delete all objects from a class, such as wh $mySchema->purge(); ``` +### Logs +`ParseLogs` allows info and error logs to be retrieved from the server as JSON. +Using the same approach as that which is utilized in the [dashboard](https://github.com/parse-community/parse-dashboard) you can view your logs with specific ranges in time, type and order. +Note that this requires the correct masterKey to be set during your initialization for access. +```php +// get last 100 info logs, sorted in descending order +$logs = ParseLogs::getInfoLogs(); + +// get last 100 info logs, sorted in descending order +$logs = ParseLogs::getErrorLogs(); + +// logs can be retrieved with further specificity +// get 10 logs from a date up to a date in ascending order +$logs = ParseLogs::getInfoLogs(10, $fromDate, $untilDate, 'asc'); + +// above can be done for 'getErrorLogs' as well +``` + + ## Contributing / Testing See [CONTRIBUTING](CONTRIBUTING.md) for information on testing and contributing to diff --git a/src/Parse/HttpClients/ParseStreamHttpClient.php b/src/Parse/HttpClients/ParseStreamHttpClient.php index 468a0079..61d033c2 100644 --- a/src/Parse/HttpClients/ParseStreamHttpClient.php +++ b/src/Parse/HttpClients/ParseStreamHttpClient.php @@ -208,16 +208,7 @@ public function send($url, $method = 'GET', $data = array()) if (isset($data) && $data != "{}") { if ($method == "GET") { // handle GET - $query = http_build_query($data, null, '&'); - - if (!defined('HHVM_VERSION')) { - $this->options['http']['content'] = $query; - } else { - // HHVM doesn't reapply 'content' to the url - // have to do it ourselves - $url.='?'.$query; - } - + $url.='?'.http_build_query($data, null, '&'); $this->addRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } elseif ($method == "POST") { // handle POST diff --git a/src/Parse/ParseLogs.php b/src/Parse/ParseLogs.php new file mode 100644 index 00000000..99ae6bcb --- /dev/null +++ b/src/Parse/ParseLogs.php @@ -0,0 +1,89 @@ + + * @package Parse + */ +class ParseLogs +{ + + /** + * Requests script logs from the server + * + * @param string $level Level of logs to return (info/error), default is info + * @param int $size Number of rows to return, default is 100 + * @param null $from Earliest logs to return from, defaults to 1 week ago + * @param null $until Latest logs to return from, defaults to current time + * @param null $order Order to sort logs by (asc/desc), defaults to descending + * @return array + */ + public static function getScriptLogs( + $level = 'info', + $size = 100, + $from = null, + $until = null, + $order = null + ) { + $data = [ + 'level' => $level, + 'size' => $size, + ]; + + if (isset($from) && $from instanceof \DateTime) { + $data['from'] = ParseClient::getProperDateFormat($from); + } + + if (isset($until) && $until instanceof \DateTime) { + $data['until'] = ParseClient::getProperDateFormat($until); + } + + if (isset($order)) { + $data['order'] = $order; + } + + $response = ParseClient::_request( + 'GET', + 'scriptlog', + null, + $data, + true + ); + + return $response; + } + + /** + * Returns info logs + * + * @param int $size Lines to return, 100 by default + * @param null $from Earliest logs to return from, default is 1 week ago + * @param null $until Latest logs to return from, defaults to current time + * @param null $order Order to sort logs by (asc/desc), defaults to descending + * @return array + */ + public static function getInfoLogs($size = 100, $from = null, $until = null, $order = null) + { + return self::getScriptLogs('info', $size, $from, $until, $order); + } + + /** + * Returns error logs + * + * @param int $size Lines to return, 100 by default + * @param null $from Earliest logs to return from, default is 1 week ago + * @param null $until Latest logs to return from, defaults to current time + * @param null $order Order to sort logs by (asc/desc), defaults to descending + * @return array + */ + public static function getErrorLogs($size = 100, $from = null, $until = null, $order = null) + { + return self::getScriptLogs('error', $size, $from, $until, $order); + } +} diff --git a/tests/Parse/ParseLogsTest.php b/tests/Parse/ParseLogsTest.php new file mode 100644 index 00000000..eb1b5aad --- /dev/null +++ b/tests/Parse/ParseLogsTest.php @@ -0,0 +1,119 @@ +assertNotEmpty($logs); + $this->assertEquals(1, count($logs)); + } + + /** + * @group parse-logs-tests + */ + public function testGettingOneLog() + { + $logs = ParseLogs::getInfoLogs(1); + $this->assertEquals(1, count($logs)); + $this->assertEquals($logs[0]['method'], 'GET'); + $this->assertTrue(isset($logs[0]['url'])); + } + + /** + * @group parse-logs-tests + */ + public function testGettingErrorLogs() + { + // Generate an error by requesting a non-existant password reset, to verify we have at least 1 line in our logs + try { + ParseUser::requestPasswordReset('not_a_real_email'); + } catch (ParseException $pe) { + // do nothing + } + + $logs = ParseLogs::getErrorLogs(1); + $this->assertEquals(1, count($logs)); + $this->assertEquals($logs[0]['code'], 205); + $this->assertEquals($logs[0]['message'], 'No user found with email not_a_real_email.'); + $this->assertEquals($logs[0]['level'], 'error'); + $this->assertTrue(isset($logs[0]['timestamp'])); + } + + /** + * @group parse-logs-tests + */ + public function testFrom() + { + // test getting logs from 4 hours in the future + $date = new \DateTime(); + $date->add(new \DateInterval('PT4H')); + $logs = ParseLogs::getInfoLogs(1, $date); + $this->assertEquals(0, count($logs)); + } + + /** + * @group parse-logs-tests + */ + public function testUntil() + { + // test getting logs from 1950 years in the past (not likely...) + $date = new \DateTime(); + $date->sub(new \DateInterval('P1950Y')); + $logs = ParseLogs::getInfoLogs(1, null, $date); + $this->assertEquals(0, count($logs)); + } + + /** + * @group parse-logs-tests + */ + public function testOrderAscending() + { + $logs = ParseLogs::getInfoLogs(15, null, null, 'asc'); + $this->assertEquals(15, count($logs)); + + $timestamp1 = $logs[0]['timestamp']; + $timestamp2 = $logs[count($logs)-1]['timestamp']; + + $timestamp1 = preg_replace('/Z$/', '', $timestamp1); + $timestamp2 = preg_replace('/Z$/', '', $timestamp2); + + // get first 2 entries + $entryDate1 = \DateTime::createFromFormat('Y-m-d\TH:i:s.u', $timestamp1); + $entryDate2 = \DateTime::createFromFormat('Y-m-d\TH:i:s.u', $timestamp2); + + $this->assertTrue($entryDate1 < $entryDate2); + } +}