From 6f246f224f9011a09930225e87533f170130f476 Mon Sep 17 00:00:00 2001 From: cindreta Date: Fri, 24 Apr 2020 21:59:08 +0200 Subject: [PATCH] Initial commit --- README.md | 45 ++++++ composer.json | 26 ++++ src/Treblle.php | 403 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 474 insertions(+) create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/Treblle.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..769b488 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Treblle for PHP +Treblle makes it super easy to understand what’s going on with your APIs and the apps that use them. To get started with Treblle create a FREE account on . + +## Requirements +* PHP 5.5+ + +## Dependencies +* [`guzzlehttp/guzzle`](https://packagist.org/packages/guzzlehttp/guzzle) + +## Installation +You can install Treblle via [Composer](http://getcomposer.org/). Simply run the following command: +```bash +$ composer require treblle/treblle-php +``` +Don't forget to [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading) composer to your project by including the following code: + +```php +require_once('vendor/autoload.php'); +``` + +## Getting started +The first thing you need to do is create a FREE account on to get an API key and Project ID. After that all you need to do is add the following line of code to your PHP API project: + +```php +$treblle = new Treblle\Bass('YOUR_API_KEY', 'YOUR_PROJECT_ID'); +``` +That's it. Your API requests and responses are now being sent to your Treblle project. Just by adding that line of code you get features like: auto-documentation, real-time request/response monitoring, error tracking and so much more. + +Treblle will catch everything that is sent to your API endpoints as well as everything that the endpoints return. In case you wish to add even more information to track specific things in your API but NOT return them in the response you can call add meta information to a specific API endpoint or all endpoints. To do so you can do the following: + +The fourth parameter is an `$options` array. The additional options are: + +```php +$treblle = new Treblle\Bass('YOUR_API_KEY', 'YOUR_PROJECT_ID'); +$treblle->addMeta('pricing', array('price_per_item' => 100, 'number_of_items' => '2', 'total' => 200)); +``` + +The setMeta method takes in two parameters. The first one is the name of your meta information and the second one is an array where you can add ANY information you want. Treblle will make sure that this is attached to the request and you will always be able to see it and search for it. + +## Support +If you have problems adding, installng or using Treblle feel free to reach out via or contact vedran@flip.hr and we will make sure to do a FREE integration for you. + +## License +Copyright 2020, Treblle. Licensed under the MIT license: +http://www.opensource.org/licenses/mit-license.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..44586a8 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "treblle/treblle-php", + "description" : "Stay in tune with your APIs", + "homepage": "https://treblle.com/", + "keywords": ["treblle", "api", "monitoring", "loggnig", "debuging", "documentation", "app development"], + "license": "MIT", + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.0" + }, + "autoload": { + "psr-4": { + "Treblle\\": "src/" + } + }, + "config": { + "preferred-install": "dist" + }, + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/src/Treblle.php b/src/Treblle.php new file mode 100644 index 0000000..0b9db73 --- /dev/null +++ b/src/Treblle.php @@ -0,0 +1,403 @@ +api_key = $api_key; + $this->project_id = $project_id; + + if(!class_exists('\GuzzleHttp\Client')) { + throw new \Exception('Treblle needs the Guzzle HTTP client to work. Please run: composer require guzzlehttp/guzzle'); + } + + $this->guzzle = new \GuzzleHttp\Client; + + $this->payload = array( + 'api_key' => $this->api_key, + 'project_id' => $this->project_id, + 'version' => 0.3, + 'data' => array( + 'server' => array( + 'timezone' => $this->getTimezone(), + 'os' => php_uname(), + 'language' => 'php-'.phpversion(), + 'sapi' => PHP_SAPI, + 'software' => $this->getServerVariable('SERVER_SOFTWARE'), + 'signature' => $this->getServerVariable('SERVER_SIGNATURE'), + 'protocol' => $this->getServerVariable('SERVER_PROTOCOL') + ), + 'request' => array( + 'timestamp' => $this->getTimestamp(), + 'ip' => $this->getClientIpAddress(), + 'url' => $this->getEndpointUrl(), + 'user_agent' => $this->getServerVariable('HTTP_USER_AGENT'), + 'method' => $this->getServerVariable('REQUEST_METHOD'), + 'headers' => getallheaders(), + 'body' => $this->maskFields($_REQUEST), + 'raw' => $this->maskFields(json_decode(file_get_contents('php://input'))) + ), + 'response' => array( + 'code' => http_response_code(), + 'size' => 0, + 'load_time' => 0, + 'body' => null + ), + 'errors' => array(), + 'git' => $this->getGitCommit(), + 'meta' => null + ) + ); + + ob_start(); + + set_error_handler(array($this, 'onError')); + set_exception_handler(array($this, 'onException')); + register_shutdown_function(array($this, 'onShutdown')); + } + + + /** + * Capture PHP errors + * @param type $type + * @param type $message + * @param type $file + * @param type $line + * @return void + */ + public function onError($type, $message, $file, $line) { + + array_push($this->payload['data']['errors'], + array( + 'source' => 'onError', + 'type' => $this->translateErrorType($type), + 'message' => $message, + 'file' => $file, + 'line' => $line + ) + ); + } + + /** + * Capture PHP exceptions + * @param type $exception + * @return void + */ + public function onException($exception) { + + array_push($this->payload['data']['errors'], + array( + 'source' => 'onException', + 'type' => 'UNHANDLED_EXCEPTION', + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine() + ) + ); + + } + + /** + * Process the log when PHP is finished processing + * @return void + */ + public function onShutdown() { + + $this->payload['data']['response']['load_time'] = $this->getLoadTime(); + $response_size = ob_get_length(); + + $error = error_get_last(); + + if(!is_null($error)) { + if($error['type'] == E_ERROR || $error['type'] == E_PARSE) { + array_push($this->payload['data']['errors'], + array( + 'source' => 'onShutdown', + 'type' => $this->translateErrorType($error['type']), + 'message' => $error['message'], + 'file' => $error['file'], + 'line' => $error['line'] + ) + ); + } + } + + if($response_size >= 2000000) { + + array( + 'source' => 'onShutdown', + 'type' => 'E_USER_ERROR', + 'message' => 'JSON response size is over 2MB', + 'file' => null, + 'line' => null + ); + + } else { + + $decoded_response = json_decode(ob_get_flush()); + + if(json_last_error() == JSON_ERROR_NONE) { + + $this->payload['data']['response']['body'] = $decoded_response; + $this->payload['data']['response']['size'] = $response_size; + } else { + array_push($this->payload['data']['errors'], + array( + 'source' => 'onShutdown', + 'type' => 'INVALID_JSON', + 'message' => 'Invalid JSON format', + 'file' => null, + 'line' => null + ) + ); + } + + } + + $this->guzzle->request('POST', 'https://rocknrolla.treblle.com', [ + 'verify' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + 'x-api-key' => $this->api_key + ], + 'body' => json_encode( + array( + 'body' => $this->payload + ) + ) + ]); + } + + /** + * Get the IP address of the requester + * @return string + */ + public function getClientIpAddress() { + + if(!empty( $_SERVER['HTTP_CLIENT_IP'])) { + $ip_address = $_SERVER['HTTP_CLIENT_IP']; + } else if(!empty( $_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; + } else { + $ip_address = $_SERVER['REMOTE_ADDR']; + } + + return $ip_address; + + } + + /** + * Get the current endpoint url + * @return string + */ + public function getEndpointUrl() { + + if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') { + $is_secure = 'https://'; + } else { + $is_secure = 'http://'; + } + + return $is_secure.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; + + } + + /** + * Get PHP global variables + * return @array + */ + public function getServerVariable($variable) { + + if(isset($_SERVER[$variable])) { + return $_SERVER[$variable]; + } else { + return null; + } + + } + + /** + * Calculate the execution time for the script + * @return float + */ + public function getLoadTime() { + if(isset($_SERVER['REQUEST_TIME_FLOAT'])) { + return (float) microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; + } else { + return (float) 0.0000; + } + } + + /** + * Get git commit information + * return @array + */ + public function getGitCommit() { + + exec('git rev-list --format=%B --max-count=1 HEAD', $commit); + + if(!empty($commit)) { + return array( + 'commit' => trim(ltrim($commit[0], 'commit')), + 'message' => $commit[1] + ); + } else { + return null; + } + + } + + /** + * Get current timestamp + * + * return @string + */ + public function getTimestamp() { + + $now = new \DateTime('UTC'); + return $now->format('Y-m-d H:i:s'); + } + + public function getTimezone() { + + $timezone = 'UTC'; + + if (ini_get('date.timezone')) { + $timezone = ini_get('date.timezone'); + } + + return $timezone; + } + + + /** + * Set log meta data + * @return void + */ + public function addMeta($meta_1 = null, $meta_2 = null) { + + if(!is_array($this->payload['data']['meta'])) { + $this->payload['data']['meta'] = array(); + } + + if(!is_null($meta_1) && !is_null($meta_2)) { + $this->payload['data']['meta'][$meta_1] = $meta_2; + } + } + + /** + * Translate error type + * @return string + */ + public function translateErrorType($type) { + + switch($type) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return null; + } + + /** + * Mask fields + * @return array + */ + public function maskFields($data) { + + $fields = ['password', 'pwd', 'secret', 'password_confirmation']; + + if(!is_array($data)) { + return; + } + + foreach ($data as $key => $value) { + + foreach ($fields as $field) { + + if(preg_match('/'.$field.'/mi', $key)) { + $data[$key] = str_repeat('*', strlen($value)); + continue; + } + + if(is_array($value)) { + $this->maskFields($data[$key]); + } + } + } + + return $data; + } + + + +}