PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / features / class-dtracer.php

class-dtracer.php in DecaLog 4.4.0, at includes/features/class-dtracer.php

622 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * DecaLog tracer definition.
4 *
5 * @package Features
6 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
7 * @since 3.0.0
8 */
9
10 namespace Decalog\Plugin\Feature;
11
12 use Decalog\System\Blog;
13 use Decalog\System\Hash;
14 use Decalog\System\Option;
15 use Decalog\System\Environment;
16 use Decalog\Logger;
17 use Decalog\Plugin\Feature\ClassTypes;
18 use Decalog\System\Markdown;
19 use Decalog\Listener\AbstractListener;
20 use Decalog\System\User;
21 use Decalog\System\UUID;
22 use Decalog\System\IP;
23
24 /**
25 * Main DecaLog tracer class.
26 *
27 * This class defines all code necessary to trace with DecaLog.
28 *
29 * @package Features
30 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
31 * @since 3.0.0
32 */
33 class DTracer {
34
35 /**
36 * The class of the component.
37 *
38 * @since 3.0.0
39 * @var string $class Maintains the class of the component.
40 */
41 protected $class = 'unknwon';
42
43 /**
44 * The name of the component.
45 *
46 * @since 3.0.0
47 * @var string $class Maintains the name of the component.
48 */
49 protected $name = 'unknown';
50
51 /**
52 * The version of the component.
53 *
54 * @since 3.0.0
55 * @var string $version Maintains the version of the component.
56 */
57 protected $version = '-';
58
59 /**
60 * The user ID.
61 *
62 * @since 3.0.0
63 * @var integer $siteid Maintains the user ID.
64 */
65 protected $siteid = -1;
66
67 /**
68 * The site ID.
69 *
70 * @since 3.0.0
71 * @var integer $userid Maintains the site ID.
72 */
73 protected $userid = -1;
74
75 /**
76 * The session ID.
77 *
78 * @since 3.0.0
79 * @var string $sessionid Maintains the session ID.
80 */
81 protected $sessionid = '';
82
83 /**
84 * The remote IP.
85 *
86 * @since 3.0.0
87 * @var string $ip Maintains the remote IP.
88 */
89 protected $ip = '';
90
91 /**
92 * Is logger allowed to run.
93 *
94 * @since 3.0.0
95 * @var boolean $allowed Maintains the allowed status of the monitor.
96 */
97 private $allowed = true;
98
99 /**
100 * Classes to exclude.
101 *
102 * @since 3.0.0
103 * @var array $skip_classes List of class partials.
104 */
105 private $skip_classes = [
106 'DLMonolog\\',
107 'Decalog\\',
108 'DecaLog\\',
109 'System\\Logger',
110 'Feature\\DecaLog',
111 'Feature\\Capture',
112 ];
113
114 /**
115 * Functions to exclude.
116 *
117 * @since 2.4.0
118 * @var array $skip_functions List of functions.
119 */
120 private $skip_functions = [
121 'call_user_func',
122 'call_user_func_array',
123 ];
124
125 /**
126 * The traces registry.
127 *
128 * @since 3.0.0
129 * @var array $traces_registry Maintains the traces definitions.
130 */
131 private static $traces_registry = [];
132
133 /**
134 * The traces ready to use.
135 *
136 * @since 3.0.0
137 * @var array $traces Maintains the spans list.
138 */
139 private static $traces = [];
140
141 /**
142 * The internal logger.
143 *
144 * @since 3.0.0
145 * @var \Decalog\Logger $logger Maintains the logger.
146 */
147 private static $logger = null;
148
149 /**
150 * Is the first initialization done?
151 *
152 * @since 3.0.0
153 * @var boolean $self_initialized Is the first initialization done?
154 */
155 private static $self_initialized = false;
156
157 /**
158 * Is the closing done?
159 *
160 * @since 3.0.0
161 * @var boolean $self_closed Is the closing done?
162 */
163 private static $self_closed = false;
164
165 /**
166 * WP root ID.
167 *
168 * @since 3.0.0
169 * @var string $wp_root_id WP root ID.
170 */
171 private static $wp_root_id = null;
172
173 /**
174 * IDs stack.
175 *
176 * @since 3.6.0
177 * @var string $stack The IDs stack.
178 */
179 private static $stack = [];
180
181 /**
182 * Initialize the class and set its properties.
183 *
184 * @param string $class The class identifier, must be in ClassTypes::$classes.
185 * @param string $name Optional. The name of the component.
186 * @param string $version Optional. The version of the component.
187 * @since 3.0.0
188 */
189 public function __construct( $class, $name = null, $version = null ) {
190 if ( ! isset( self::$logger ) && class_exists( '\Decalog\Logger' ) ) {
191 self::$logger = new \Decalog\Logger( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
192 }
193 if ( ! isset( self::$logger ) && ! class_exists( '\Decalog\Logger' ) ) {
194 self::$logger = new \Psr\Log\NullLogger();
195 }
196 if ( ! Option::network_get( 'autolisteners' ) ) {
197 $this->allowed = in_array( 'trace', Option::network_get( 'listeners' ), true );
198 }
199 if ( $this->allowed ) {
200 if ( in_array( $class, ClassTypes::$classes, true ) ) {
201 $this->class = $class;
202 }
203 if ( $name && is_string( $name ) ) {
204 $this->name = $name;
205 }
206 if ( $version && is_string( $version ) ) {
207 $this->version = $version;
208 }
209 $this->ip = IP::get_current();
210 self::$logger->debug( 'A new instance of DecaLog tracing is initialized and operational.' );
211 } else {
212 self::$logger->debug( 'Skipped initialization of a DecaLog tracing.' );
213 }
214 if ( ! self::$self_initialized ) {
215 self::$self_initialized = true;
216 if ( ! defined( 'DECALOG_TRACEID' ) ) {
217 define( 'DECALOG_TRACEID', UUID::generate_unique_id( 32 ) );
218 }
219 $this->init_root();
220 }
221 }
222
223 /**
224 * Starts a span.
225 *
226 * @param string $name The name of the span.
227 * @param string $parent_id Optional. The id of the parent. If none, it will be linked to WP root id.
228 * @return string Id of started span.
229 * @since 3.0.0
230 */
231 public function start_span( $name, $parent_id = 'xxx' ) {
232 return $this->start_span_with_id( $name, null, $parent_id );
233 }
234
235 /**
236 * Starts a span.
237 *
238 * @param string $name The name of the span.
239 * @param string $id Optional. The id of the span.
240 * @param string $parent_id Optional. The id of the parent. If none, it will be linked to WP root id.
241 * @param int $timestamp Optional. The microsecond timestamp at which the span started.
242 * @return string Id of started span.
243 * @since 3.0.0
244 */
245 public function start_span_with_id( $name, $id, $parent_id = 'xxx', $timestamp = 0 ) {
246 if ( 'auto' === $parent_id ) {
247 $parent_id = $this->get_auto_parent_id( $timestamp );
248 }
249 if ( ! array_key_exists( $parent_id, self::$traces_registry ) ) {
250 $parent_id = self::$wp_root_id;
251 } else {
252 $parent_id = self::$traces_registry[ $parent_id ]['id'];
253 }
254 $span = $this->init_span( $id );
255 $span['parentId'] = $parent_id;
256 $span['name'] = $span['name'] . $name;
257 $span['tags'] = array_merge( $span['tags'], $this->introspection_data() );
258 self::$traces_registry[ $span['id'] ] = $span;
259 self::$stack[] = $span['id'];
260 return $span['id'];
261 }
262
263 /**
264 * Try to get parent Id.
265 *
266 * @param int $timestamp The microsecond timestamp at which the span started.
267 * @return string Id of the parent span.
268 * @since 3.6.0
269 */
270 protected function get_auto_parent_id( $timestamp ) {
271 if ( 0 < count( self::$stack ) ) {
272 return self::$stack[ array_key_last( self::$stack ) ];
273 }
274 $mark = 0;
275 $probable_id = 'unknown';
276 foreach ( self::$traces_registry as $key => $span ) {
277 if ( 'Core' === $span['localEndpoint']['serviceName'] && $timestamp >= $span['timestamp'] && $timestamp > $mark ) {
278 if ( 0 === $span['duration'] ) {
279 $mark = $span['timestamp'];
280 $probable_id = $key;
281 } else {
282 if ( $timestamp < $span['timestamp'] + $span['duration'] ) {
283 $mark = $span['timestamp'];
284 $probable_id = $key;
285 }
286 }
287 }
288 }
289 return $probable_id;
290 }
291
292 /**
293 * Ends a span.
294 *
295 * @param string $id The id of the span.
296 * @since 3.0.0
297 */
298 public function end_span( $id ) {
299 if ( array_key_exists( $id, self::$traces_registry ) ) {
300 self::$traces_registry[ $id ]['duration'] = (int) ( ( 1000000 * microtime( true ) ) - self::$traces_registry[ $id ]['timestamp'] );
301 }
302 if ( in_array( $id, self::$stack, true ) ) {
303 do {
304 $key = array_pop( self::$stack );
305 } while ( $id !== $key );
306 }
307 }
308
309 /**
310 * Starts, set and ends a span.
311 *
312 * @param string $name The name of the span.
313 * @param array $values Optional. The values to add to the span.
314 * @since 3.6.0
315 */
316 public function inject_span( $name, $values ) {
317 $id = $this->start_span_with_id( $name, null, 'auto', array_key_exists( 'timestamp', $values ) ? $values['timestamp'] : 0 );
318 foreach ( $values as $key => $value ) {
319 if ( 'tags' === $key ) {
320 if ( is_array( $value ) ) {
321 foreach ( $value as $k => $v ) {
322 self::$traces_registry[ $id ]['tags'][ (string) $k ] = (string) $v;
323 }
324 }
325 } else {
326 self::$traces_registry[ $id ][ $key ] = $value;
327 }
328 }
329 array_pop( self::$stack );
330 }
331
332 /**
333 * Initializes a span.
334 *
335 * @param string $id Optional. The forced Id.
336 * @return array An initialized span.
337 * @since 3.0.0
338 */
339 private function init_span( $id = null ) {
340 return [
341 'id' => $id ?? UUID::generate_unique_id( 8 ),
342 'traceId' => DECALOG_TRACEID,
343 'parentId' => '',
344 'name' => $this->name . ' / ',
345 'timestamp' => (int) ( 1000000 * microtime( true ) ),
346 'duration' => 0,
347 'localEndpoint' => [
348 'serviceName' => ucwords( $this->class ),
349 ],
350 'remoteEndpoint' => [],
351 'tags' => [
352 'component.class' => $this->class,
353 'component.name' => $this->name,
354 'component.version' => $this->version,
355 ],
356 ];
357 }
358
359 /**
360 * Initializes traces root.
361 *
362 * @since 3.0.0
363 */
364 private function init_root() {
365 if ( ! defined( 'POWP_START_TIMESTAMP' ) ) {
366 define( 'POWP_START_TIMESTAMP', microtime( true ) );
367 }
368 if ( ! defined( 'POWS_START_TIMESTAMP' ) ) {
369 if ( array_key_exists( 'REQUEST_TIME_FLOAT', $_SERVER ) ) {
370 define( 'POWS_START_TIMESTAMP', (float) filter_var( $_SERVER['REQUEST_TIME_FLOAT'], FILTER_VALIDATE_FLOAT ) );
371 } else {
372 define( 'POWS_START_TIMESTAMP', POWP_START_TIMESTAMP );
373 }
374 }
375 if ( ! defined( 'POMU_END_TIMESTAMP' ) ) {
376 define( 'POMU_END_TIMESTAMP', microtime( true ) );
377 }
378 if ( ! defined( 'POPL_START_TIMESTAMP' ) ) {
379 define( 'POPL_START_TIMESTAMP', microtime( true ) );
380 }
381 if ( ! defined( 'DECALOG_SPAN_MUPLUGINS_LOAD' ) ) {
382 define( 'DECALOG_SPAN_MUPLUGINS_LOAD', UUID::generate_unique_id( 8 ) );
383 }
384 if ( ! defined( 'DECALOG_SPAN_PLUGINS_LOAD' ) ) {
385 define( 'DECALOG_SPAN_PLUGINS_LOAD', UUID::generate_unique_id( 8 ) );
386 }
387 if ( ! defined( 'DECALOG_SPAN_THEME_SETUP' ) ) {
388 define( 'DECALOG_SPAN_THEME_SETUP', UUID::generate_unique_id( 8 ) );
389 }
390 if ( ! defined( 'DECALOG_SPAN_USER_AUTHENTICATION' ) ) {
391 define( 'DECALOG_SPAN_USER_AUTHENTICATION', UUID::generate_unique_id( 8 ) );
392 }
393 if ( ! defined( 'DECALOG_SPAN_PLUGINS_INITIALIZATION' ) ) {
394 define( 'DECALOG_SPAN_PLUGINS_INITIALIZATION', UUID::generate_unique_id( 8 ) );
395 }
396 if ( ! defined( 'DECALOG_SPAN_MAIN_RUN' ) ) {
397 define( 'DECALOG_SPAN_MAIN_RUN', UUID::generate_unique_id( 8 ) );
398 }
399 if ( ! defined( 'DECALOG_SPAN_SHUTDOWN' ) ) {
400 define( 'DECALOG_SPAN_SHUTDOWN', UUID::generate_unique_id( 8 ) );
401 }
402 // Root
403 $root = $this->init_span();
404 $root['name'] = 'CALL:' . $this->channel_tag( Environment::exec_mode() );
405 $root['localEndpoint']['serviceName'] = 'Main Request';
406 $root['timestamp'] = (int) ( 1000000 * POWS_START_TIMESTAMP );
407 $root['tags'] = $this->www_data();
408 $root['kind'] = 'SERVER';
409 unset( $root['parentId'] );
410 self::$traces_registry['ROOT'] = $root;
411 // Server Init
412 if ( 0 < POWP_START_TIMESTAMP - POWS_START_TIMESTAMP ) {
413 $init = $this->init_span();
414 $init['parentId'] = $root['id'];
415 $init['name'] = 'Initialization';
416 $init['localEndpoint']['serviceName'] = 'Server';
417 $init['tags'] = [];
418 $init['timestamp'] = (int) ( 1000000 * POWS_START_TIMESTAMP );
419 $init['duration'] = (int) ( 1000000 * ( POWP_START_TIMESTAMP - POWS_START_TIMESTAMP ) );
420 self::$traces_registry['INIT'] = $init;
421 }
422 // WordPress execution
423 $wp = $this->init_span();
424 $wp['parentId'] = $root['id'];
425 $wp['name'] = 'Execution';
426 $wp['localEndpoint']['serviceName'] = 'WordPress';
427 $wp['tags'] = [];
428 $wp['timestamp'] = (int) ( 1000000 * POWP_START_TIMESTAMP );
429 self::$traces_registry[ $wp['id'] ] = $wp;
430 self::$wp_root_id = $wp['id'];
431 // WordPress full load
432 $wpfl = $this->init_span();
433 $wpfl['parentId'] = $wp['id'];
434 $wpfl['name'] = 'WordPress / Load';
435 $wpfl['localEndpoint']['serviceName'] = 'Core';
436 $wpfl['timestamp'] = (int) ( 1000000 * POWP_START_TIMESTAMP );
437 self::$traces_registry['WPFL'] = $wpfl;
438 // WordPress load
439 $wpl = $this->init_span();
440 $wpl['id'] = DECALOG_SPAN_MUPLUGINS_LOAD;
441 $wpl['parentId'] = $wpfl['id'];
442 $wpl['name'] = 'WordPress / Core & MU-Plugins Load';
443 $wpl['localEndpoint']['serviceName'] = 'Core';
444 $wpl['timestamp'] = (int) ( 1000000 * POWP_START_TIMESTAMP );
445 $wpl['duration'] = (int) ( 1000000 * ( POMU_END_TIMESTAMP - POWP_START_TIMESTAMP ) );
446 self::$traces_registry[ $wpl['id'] ] = $wpl;
447 // Plugins load
448 $wpl = $this->init_span();
449 $wpl['id'] = DECALOG_SPAN_PLUGINS_LOAD;
450 $wpl['parentId'] = $wpfl['id'];
451 $wpl['name'] = 'WordPress / Plugins Load';
452 $wpl['localEndpoint']['serviceName'] = 'Core';
453 $wpl['timestamp'] = (int) ( 1000000 * POPL_START_TIMESTAMP );
454 self::$traces_registry[ $wpl['id'] ] = $wpl;
455 }
456
457 /**
458 * Initializes traces root.
459 *
460 * @since 3.0.0
461 */
462 public static function plugins_loaded() {
463 self::$traces_registry[ DECALOG_SPAN_PLUGINS_LOAD ]['duration'] = (int) ( ( 1000000 * microtime( true ) ) - self::$traces_registry[ DECALOG_SPAN_PLUGINS_LOAD ]['timestamp'] );
464 }
465
466 /**
467 * Verify if a trace must be skipped.
468 *
469 * @param array $trace The trace to verify.
470 * @param integer $index The index of the trace to verify.
471 * @return boolean True if the record must be skipped, false otherwise.
472 * @since 3.0.0
473 */
474 private function is_skipped( array $trace, int $index ) {
475 if ( ! isset( $trace[ $index ] ) ) {
476 return false;
477 }
478 return isset( $trace[ $index ]['class'] ) || in_array( $trace[ $index ]['function'], $this->skip_functions, true );
479 }
480
481 /**
482 * Get the channel tag.
483 *
484 * @param integer $id Optional. The channel id (execution mode).
485 * @return string The channel tag.
486 * @since 3.0.0
487 */
488 private function channel_tag( $id = 0 ) {
489 if ( $id >= count( ChannelTypes::$channels ) ) {
490 $id = 0;
491 }
492 return ChannelTypes::$channels[ $id ];
493 }
494
495 /**
496 * Data to add for introspection processing.
497 *
498 * @return array The introspection records.
499 * @since 3.0.0
500 */
501 private function introspection_data() {
502 // phpcs:ignore
503 $trace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS );
504 array_shift( $trace );
505 array_shift( $trace );
506 $i = 0;
507 while ( $this->is_skipped( $trace, $i ) ) {
508 if ( isset( $trace[ $i ]['class'] ) ) {
509 foreach ( $this->skip_classes as $part ) {
510 if ( strpos( $trace[ $i ]['class'], $part ) !== false ) {
511 $i++;
512 continue 2;
513 }
514 }
515 } elseif ( in_array( $trace[ $i ]['function'], $this->skip_functions, true ) ) {
516 $i++;
517 continue;
518 }
519 break;
520 }
521 return [
522 'php.file' => $trace[ $i - 1 ]['file'] ?? null,
523 'php.line' => $trace[ $i - 1 ]['line'] ?? null,
524 'php.class' => $trace[ $i ]['class'] ?? null,
525 'php.function' => $trace[ $i ]['function'] ?? null,
526 ];
527 }
528
529 /**
530 * Data to add for WordPress processing.
531 *
532 * @return array The WordPress records.
533 * @since 3.0.0
534 */
535 private function wordpress_data() {
536 if ( -1 === $this->siteid ) {
537 $this->siteid = Blog::get_current_blog_id( 0 );
538 }
539 if ( -1 === $this->userid ) {
540 $this->userid = User::get_current_user_id( 0 );
541 }
542 if ( '' === $this->sessionid && function_exists( 'wp_parse_auth_cookie' ) ) {
543 $this->sessionid = Hash::simple_hash( wp_get_session_token(), false );
544 }
545 return [
546 'wp.siteid' => $this->siteid,
547 'wp.userid' => $this->userid,
548 'wp.sessionid' => $this->sessionid,
549 'wp.remoteip' => $this->ip,
550 ];
551 }
552
553 /**
554 * Data to add for WordPress processing.
555 *
556 * @return array The WordPress records.
557 * @since 3.0.0
558 */
559 private function www_data() {
560 $result = [
561 'http.remoteip' => $this->ip,
562 ];
563 if ( array_key_exists( 'HTTP_USER_AGENT', $_SERVER ) ) {
564 $result['http.useragent'] = filter_input( INPUT_SERVER, 'HTTP_USER_AGENT' );
565 }
566 if ( array_key_exists( 'REQUEST_URI', $_SERVER ) ) {
567 $result['http.uri'] = filter_input( INPUT_SERVER, 'REQUEST_URI' );
568 }
569 if ( array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
570 $result['http.method'] = filter_input( INPUT_SERVER, 'REQUEST_METHOD' );
571 }
572 if ( array_key_exists( 'HTTP_REFERER', $_SERVER ) ) {
573 $result['http.referer'] = filter_input( INPUT_SERVER, 'HTTP_REFERER' );
574 }
575 return $result;
576 }
577
578 /**
579 * Get the traces registry.
580 *
581 * @return array The registry;
582 * @since 3.0.0
583 */
584 public function traces() {
585 if ( ! $this->allowed ) {
586 return [];
587 }
588 $this->before_close();
589 return self::$traces;
590 }
591
592 /**
593 * Computes traces.
594 *
595 * @since 3.0.0
596 */
597 private function before_close() {
598 if ( self::$self_closed ) {
599 return;
600 }
601 self::$self_closed = true;
602 if ( ! defined( 'POWP_END_TIMESTAMP' ) ) {
603 define( 'POWP_END_TIMESTAMP', microtime( true ) );
604 }
605 $end = (int) ( 1000000 * POWP_END_TIMESTAMP );
606 foreach ( self::$traces_registry as $tid => $span ) {
607 if ( 'ROOT' !== $tid && 'INIT' !== $tid ) {
608 $span['tags'] = array_merge( $span['tags'], $this->wordpress_data(), $this->www_data() );
609 }
610 if ( 0 === $span['duration'] ) {
611 $span['duration'] = $end - $span['timestamp'];
612 }
613 foreach ( $span as $key => $value ) {
614 if ( is_null( $value ) || ( is_array( $value ) && 0 === count( $value ) ) || ( is_string( $value ) && '' === $value ) ) {
615 unset( $span[ $key ] );
616 }
617 }
618 self::$traces[] = $span;
619 }
620 }
621 }
622