AmfphpMonitor.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <?php
  2. /**
  3. * This file is part of amfPHP
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the license that is bundled
  8. * with this package in the file license.txt.
  9. */
  10. /**
  11. * includes
  12. */
  13. require_once dirname(__FILE__) . '/AmfphpMonitorService.php';
  14. /**
  15. * logs monitoring information, and makes it possible to toggle logging and retrieve the data via the AmfphpMonitorService.
  16. * If the log file is not writable or its size is superior to maxLogFileSize,
  17. * logging shall fail silently. This is designed to avoid errors being generated
  18. * when a developer forgets to turn off monitoring, and to allow the plugin to be enabled
  19. * by default
  20. *
  21. * The log file is by default at [AmfphpMonitor plugin folder]/log.txt.php
  22. * To change this set 'logPath' in the config.
  23. *
  24. *
  25. *
  26. * note: Logging multiple times with the same name is not possible!
  27. * @todo maybe change storage mechanism to sqlite. This means checking that it is indeed available, checking if performance is ok etc., so it might be a bit heavy handed.
  28. *
  29. * @author Ariel Sommeria-klein
  30. * @package Amfphp_Plugins_Monitor
  31. */
  32. class AmfphpMonitor {
  33. /**
  34. * path to log file. If it is publicly accessible
  35. * @var string
  36. */
  37. protected $logPath;
  38. /**
  39. * service and method name. If they are multiple calls in request, they are spearated with a ', '
  40. * @var string
  41. */
  42. protected $uri;
  43. /**
  44. * was there an exception during service call.
  45. * todo. unused.
  46. * @var boolean
  47. */
  48. protected $isException;
  49. /**
  50. * last measured time, or start time
  51. * @var float
  52. */
  53. protected static $lastMeasuredTime;
  54. /**
  55. * various times. for example ['startD' => 12 , 'stopD' => 30 ]
  56. * means start of deserialization at 12 ms after the request was received,
  57. * and end of deserialization 30 ms after start of deserialization.
  58. * @var array
  59. */
  60. protected static $times;
  61. /**
  62. * restrict access to amfphp_admin, the role set when using the back office. default is true.
  63. * @var boolean
  64. */
  65. protected $restrictAccess = true;
  66. /**
  67. * The maximum size of the log file, in bytes. Once the log is bigger than this, logging stops.
  68. * Note that this is not strict, it can overflow with the last log.
  69. * @var int
  70. */
  71. protected $maxLogFileSize = 1000000;
  72. /**
  73. * constructor.
  74. * manages log path. If file exists at log path, adds hooks for logging.
  75. * @param array $config
  76. */
  77. public function __construct(array $config = null) {
  78. self::$lastMeasuredTime = round(microtime(true) * 1000);
  79. self::$times = array();
  80. $filterManager = Amfphp_Core_FilterManager::getInstance();
  81. $filterManager->addFilter(Amfphp_Core_Gateway::FILTER_SERVICE_NAMES_2_CLASS_FIND_INFO, $this, 'filterServiceNames2ClassFindInfo');
  82. if (isset($config['logPath'])) {
  83. $this->logPath = $config['logPath'];
  84. }else{
  85. $this->logPath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'log.txt.php';
  86. }
  87. AmfphpMonitorService::$logPath = $this->logPath;
  88. if(isset($config['restrictAccess'])){
  89. $this->restrictAccess = $config['restrictAccess'];
  90. }
  91. AmfphpMonitorService::$restrictAccess = $this->restrictAccess;
  92. if(isset($config['maxLogFileSize'])){
  93. $this->maxLogFileSize = $config['maxLogFileSize'];
  94. }
  95. AmfphpMonitorService::$maxLogFileSize = $this->maxLogFileSize;
  96. if(!is_writable($this->logPath) || !is_readable($this->logPath)){
  97. return;
  98. }
  99. if(filesize($this->logPath) > $this->maxLogFileSize){
  100. return;
  101. }
  102. $filterManager->addFilter(Amfphp_Core_Gateway::FILTER_DESERIALIZED_REQUEST, $this, 'filterDeserializedRequest', 0);
  103. $filterManager->addFilter(Amfphp_Core_Gateway::FILTER_DESERIALIZED_RESPONSE, $this, 'filterDeserializedResponse', 0);
  104. $filterManager->addFilter(Amfphp_Core_Gateway::FILTER_SERIALIZED_RESPONSE, $this, 'filterSerializedResponse');
  105. }
  106. /**
  107. * measures time since previous call (or start time time if this the first call) , and stores it in the times array
  108. * public and static so that services can call this too to add custom times.
  109. * updates lastMeasuredTime
  110. * @param string $name
  111. */
  112. public static function addTime($name){
  113. $now = round(microtime(true) * 1000);
  114. $timeSinceLastMeasure = $now - self::$lastMeasuredTime;
  115. self::$times[$name] = $timeSinceLastMeasure;
  116. self::$lastMeasuredTime = $now;
  117. }
  118. /**
  119. * add monitor service
  120. * @param array $serviceNames2ClassFindInfo associative array of key -> class find info
  121. */
  122. public function filterServiceNames2ClassFindInfo(array $serviceNames2ClassFindInfo) {
  123. $serviceNames2ClassFindInfo['AmfphpMonitorService'] = new Amfphp_Core_Common_ClassFindInfo(dirname(__FILE__) . '/AmfphpMonitorService.php', 'AmfphpMonitorService');
  124. return $serviceNames2ClassFindInfo;
  125. }
  126. /**
  127. * logs the time for end of deserialization, as well as grabs the target uris(service + method)
  128. * as each request has its own format, the code here must handle all deserialized request structures.
  129. * if case not handled just don't set target uris, as data can still be useful even without them.
  130. * @param mixed $deserializedRequest
  131. */
  132. public function filterDeserializedRequest($deserializedRequest) {
  133. self::addTime('Deserialization');
  134. //AMF
  135. if(is_a($deserializedRequest, 'Amfphp_Core_Amf_Packet')){
  136. //detect Flex by looking at first message. assumes that request doesn't mix simple AMF remoting with Flex Messaging
  137. $isFlex = ($deserializedRequest->messages[0]->targetUri == 'null'); //target Uri is described in Flex message
  138. for($i = 0; $i < count($deserializedRequest->messages); $i++){
  139. if($i > 0){
  140. //add multiple uris split with a ', '
  141. $this->uri .= ', ';
  142. }
  143. $message = $deserializedRequest->messages[$i];
  144. if ($isFlex){
  145. $flexMessage = $message->data[0];
  146. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  147. $messageType = $flexMessage->$explicitTypeField;
  148. //assumes AmfphpFlexMessaging plugin is installed, which is reasonable given that we're using Flex messaging
  149. if ($messageType == AmfphpFlexMessaging::FLEX_TYPE_COMMAND_MESSAGE) {
  150. $this->uri .= "Flex Command Message";
  151. }else if ($messageType == AmfphpFlexMessaging::FLEX_TYPE_REMOTING_MESSAGE) {
  152. $this->uri .= $flexMessage->source . '.' . $flexMessage->operation;
  153. }else{
  154. $this->uri .= 'Flex ' . $messageType;
  155. }
  156. }else{
  157. $this->uri .= $message->targetUri;
  158. }
  159. }
  160. }else if(isset ($deserializedRequest->serviceName)){
  161. //JSON
  162. $this->uri = $deserializedRequest->serviceName . '/' . $deserializedRequest->methodName;
  163. }else if(is_array($deserializedRequest) && isset ($deserializedRequest['serviceName'])){
  164. //GET, included request
  165. $this->uri = $deserializedRequest['serviceName'] . '/' . $deserializedRequest['methodName'];
  166. }
  167. }
  168. /**
  169. * logs the time for start of serialization
  170. * @param packet $deserializedResponse
  171. */
  172. public function filterDeserializedResponse($deserializedResponse) {
  173. self::addTime('Service Call');
  174. }
  175. /**
  176. * logs the time for end of serialization and writes log
  177. * ignores calls to Amfphp services (checks for 'Amfphp' at beginning of name)
  178. * tries to get a lock on the file, and if not then just drops the log.
  179. *
  180. * @param mixed $rawData
  181. */
  182. public function filterSerializedResponse($rawData) {
  183. if(substr($this->uri, 0, 6) == 'Amfphp'){
  184. return;
  185. }
  186. if(filesize($this->logPath) > $this->maxLogFileSize){
  187. return;
  188. }
  189. self::addTime('Serialization');
  190. $record = new stdClass();
  191. $record->uri = $this->uri;
  192. $record->times = self::$times;
  193. $fp = fopen($this->logPath, "a");
  194. if (flock($fp, LOCK_EX)) { // acquire an exclusive lock
  195. fwrite($fp, "\n" . serialize($record));
  196. fflush($fp); // flush output before releasing the lock
  197. flock($fp, LOCK_UN); // release the lock
  198. } else {
  199. echo "Couldn't get the lock!";
  200. }
  201. fclose($fp);
  202. }
  203. }
  204. ?>