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-wpcli.php

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

1,852 lines 57.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP-CLI for DecaLog.
4 *
5 * Adds WP-CLI commands to DecaLog
6 *
7 * @package Features
8 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
9 * @since 2.0.0
10 */
11
12 namespace Decalog\Plugin\Feature;
13
14 use Decalog\Handler\SharedMemoryHandler;
15 use Decalog\Listener\ListenerFactory;
16 use Decalog\Plugin\Feature\Log;
17 use Decalog\System\Cache;
18 use Decalog\System\Date;
19 use Decalog\System\EmojiFlag;
20 use Decalog\System\Environment;
21 use Decalog\System\Markdown;
22 use Decalog\System\Option;
23 use Decalog\System\GeoIP;
24 use Decalog\System\PHP;
25 use Decalog\System\SharedMemory;
26 use Decalog\System\Timezone;
27 use Decalog\System\UUID;
28 use Decalog\Plugin\Feature\EventTypes;
29 use Decalog\Plugin\Feature\Autolog;
30 use Prometheus\RenderTextFormat;
31 use Spyc;
32 use Decalog\Plugin\Feature\DLogger;
33 use Decalog\Plugin\Feature\SDK;
34
35 /**
36 * Manages DecaLog, view events logs and send events to loggers.
37 *
38 * @package Features
39 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
40 * @since 2.0.0
41 */
42 class Wpcli {
43
44 /**
45 * List of color format per level.
46 *
47 * @since 2.0.0
48 * @var array $level_color Level colors.
49 */
50 private $level_color = [
51 'standard' =>
52 [
53 'debug' => '',
54 'info' => '%4%c',
55 'notice' => '%4%C',
56 'warning' => '%3%r',
57 'error' => '%1%y',
58 'critical' => '%1%Y',
59 'alert' => '%F%1%Y',
60 'emergency' => '',
61 ],
62 'soft' =>
63 [
64 'debug' => '',
65 'info' => '%0%c',
66 'notice' => '%0%C',
67 'warning' => '%0%Y',
68 'error' => '%0%r',
69 'critical' => '%0%R',
70 'alert' => '%0%F%R',
71 'emergency' => '',
72 ],
73 ];
74
75 /**
76 * List of exit codes.
77 *
78 * @since 2.0.0
79 * @var array $exit_codes Exit codes.
80 */
81 private $exit_codes = [
82 0 => 'operation successful.',
83 1 => 'invalid logger type supplied.',
84 2 => 'invalid logger uuid supplied.',
85 3 => 'system loggers can\'t be managed.',
86 4 => 'unable to create a new logger.',
87 5 => 'unable to modify this logger.',
88 6 => 'invalid listener id supplied.',
89 7 => 'unrecognized setting.',
90 8 => 'unrecognized action.',
91 9 => 'invalid metric id supplied.',
92 10 => 'forbidden or unknown level.',
93 11 => 'unable to launch tail command, no shared memory manager found.',
94 12 => 'histograms can\'t be displayed in command-line mode.',
95 255 => 'unknown error.',
96 ];
97
98 /**
99 * Flush output without warnings.
100 *
101 * @since 2.0.2
102 */
103 private function flush() {
104 // phpcs:ignore
105 set_error_handler( null );
106 // phpcs:ignore
107 @ob_flush();
108 // phpcs:ignore
109 restore_error_handler();
110 }
111
112 /**
113 * Write ids as clean stdout.
114 *
115 * @param array $ids The ids.
116 * @param string $field Optional. The field to output.
117 * @since 2.0.0
118 */
119 private function write_ids( $ids, $field = '' ) {
120 $result = '';
121 $last = end( $ids );
122 foreach ( $ids as $key => $id ) {
123 if ( '' === $field ) {
124 $result .= $key;
125 } else {
126 $result .= $id[ $field ];
127 }
128 if ( $id !== $last ) {
129 $result .= ' ';
130 }
131 }
132 // phpcs:ignore
133 fwrite( STDOUT, $result );
134 }
135
136 /**
137 * Write an error.
138 *
139 * @param integer $code Optional. The error code.
140 * @param boolean $stdout Optional. Clean stdout output.
141 * @since 2.0.0
142 */
143 private function error( $code = 255, $stdout = false ) {
144 $msg = '[' . DECALOG_PRODUCT_NAME . '] ' . ucfirst( $this->exit_codes[ $code ] );
145 if ( \WP_CLI\Utils\isPiped() ) {
146 // phpcs:ignore
147 fwrite( STDOUT, '' );
148 // phpcs:ignore
149 exit( $code );
150 } elseif ( $stdout ) {
151 // phpcs:ignore
152 fwrite( STDERR, $msg );
153 // phpcs:ignore
154 exit( $code );
155 } else {
156 \WP_CLI::error( $msg );
157 }
158 }
159
160 /**
161 * Write a warning.
162 *
163 * @param string $msg The message.
164 * @param string $result Optional. The result.
165 * @param boolean $stdout Optional. Clean stdout output.
166 * @since 2.0.0
167 */
168 private function warning( $msg, $result = '', $stdout = false ) {
169 $msg = '[' . DECALOG_PRODUCT_NAME . '] ' . ucfirst( $msg );
170 if ( \WP_CLI\Utils\isPiped() || $stdout ) {
171 // phpcs:ignore
172 fwrite( STDOUT, $result );
173 } else {
174 \WP_CLI::warning( $msg );
175 }
176 }
177
178 /**
179 * Write a success.
180 *
181 * @param string $msg The message.
182 * @param string $result Optional. The result.
183 * @param boolean $stdout Optional. Clean stdout output.
184 * @since 2.0.0
185 */
186 private function success( $msg, $result = '', $stdout = false ) {
187 $msg = '[' . DECALOG_PRODUCT_NAME . '] ' . ucfirst( $msg );
188 if ( \WP_CLI\Utils\isPiped() || $stdout ) {
189 // phpcs:ignore
190 fwrite( STDOUT, $result );
191 } else {
192 \WP_CLI::success( $msg );
193 }
194 }
195
196 /**
197 * Write a wimple line.
198 *
199 * @param string $msg The message.
200 * @param string $result Optional. The result.
201 * @param boolean $stdout Optional. Clean stdout output.
202 * @since 2.0.0
203 */
204 private function line( $msg, $result = '', $stdout = false ) {
205 if ( \WP_CLI\Utils\isPiped() || $stdout ) {
206 // phpcs:ignore
207 fwrite( STDOUT, $result );
208 } else {
209 \WP_CLI::line( $msg );
210 }
211 }
212
213 /**
214 * Write a wimple log line.
215 *
216 * @param string $msg The message.
217 * @param boolean $stdout Optional. Clean stdout output.
218 * @since 2.0.0
219 */
220 private function log( $msg, $stdout = false ) {
221 if ( ! \WP_CLI\Utils\isPiped() && ! $stdout ) {
222 \WP_CLI::log( $msg );
223 }
224 }
225
226 /**
227 * Get params from command line.
228 *
229 * @param array $args The command line parameters.
230 * @return array The true parameters.
231 * @since 2.0.0
232 */
233 private function get_params( $args ) {
234 $result = '';
235 if ( array_key_exists( 'settings', $args ) ) {
236 $result = \json_decode( $args['settings'], true );
237 }
238 if ( ! $result || ! is_array( $result ) ) {
239 $result = [];
240 }
241 return $result;
242 }
243
244 /**
245 * Update processors.
246 *
247 * @param array $processors The current processors.
248 * @param string $proc The processor to set.
249 * @param boolean $value The value to set.
250 * @return array The updated processors.
251 * @since 2.0.0
252 */
253 private function updated_proc( $processors, $proc, $value ) {
254 $key = '';
255 switch ( $proc ) {
256 case 'proc_wp':
257 $key = 'WordpressProcessor';
258 break;
259 case 'proc_http':
260 $key = 'WWWProcessor';
261 break;
262 case 'proc_php':
263 $key = 'IntrospectionProcessor';
264 break;
265 case 'proc_trace':
266 $key = 'BacktraceProcessor';
267 break;
268 }
269 if ( '' !== $key ) {
270 if ( $value && ! in_array( $key, $processors, true ) ) {
271 $processors[] = $key;
272 }
273 if ( ! $value && in_array( $key, $processors, true ) ) {
274 $processors = array_diff( $processors, [ $key ] );
275 }
276 }
277 return $processors;
278 }
279
280 /**
281 * Modify a logger.
282 *
283 * @param string $uuid The logger uuid.
284 * @param array $args The command line parameters.
285 * @param boolean $start Optional. Force running mode.
286 * @return string The logger uuid.
287 * @since 2.0.0
288 */
289 private function logger_modify( $uuid, $args, $start = false ) {
290 $params = $this->get_params( $args );
291 $loggers = Option::network_get( 'loggers' );
292 $logger = $loggers[ $uuid ];
293 $handler_types = new HandlerTypes();
294 $handler = $handler_types->get( $logger['handler'] );
295 unset( $loggers[ $uuid ] );
296 foreach ( $params as $param => $value ) {
297 switch ( $param ) {
298 case 'obfuscation':
299 case 'pseudonymization':
300 $logger['privacy'][ $param ] = (bool) $value;
301 break;
302 case 'proc_wp':
303 case 'proc_http':
304 case 'proc_php':
305 case 'proc_trace':
306 $logger['processors'] = $this->updated_proc( $logger['processors'], $param, (bool) $value );
307 break;
308 case 'level':
309 if ( array_key_exists( strtolower( $value ), EventTypes::$levels ) ) {
310 $logger['level'] = EventTypes::$levels[ strtolower( $value ) ];
311 } else {
312 $logger['level'] = $handler['minimal'];
313 }
314 break;
315 case 'name':
316 $logger['name'] = esc_html( (string) $value );
317 break;
318 default:
319 if ( array_key_exists( $param, $handler['configuration'] ) ) {
320 switch ( $handler['configuration'][ $param ]['control']['cast'] ) {
321 case 'boolean':
322 $logger['configuration'][ $param ] = (bool) $value;
323 break;
324 case 'integer':
325 $logger['configuration'][ $param ] = (int) $value;
326 break;
327 case 'string':
328 $logger['configuration'][ $param ] = (string) $value;
329 break;
330 }
331 }
332 break;
333 }
334 }
335 if ( $start ) {
336 $logger['running'] = true;
337 }
338 $loggers[ $uuid ] = $logger;
339 Option::network_set( 'loggers', $loggers );
340 return $uuid;
341 }
342
343 /**
344 * Add a logger.
345 *
346 * @param string $handler The logger type.
347 * @param array $args The command line parameters.
348 * @return string The logger uuid.
349 * @since 2.0.0
350 */
351 private function logger_add( $handler, $args ) {
352 $uuid = UUID::generate_v4();
353 $logger = [
354 'uuid' => $uuid,
355 'name' => decalog_esc_html__( 'New logger', 'decalog' ),
356 'handler' => $handler,
357 'running' => false,
358 ];
359 $loggers = Option::network_get( 'loggers' );
360 $factory = new LoggerFactory();
361 $loggers[ $uuid ] = $factory->check( $logger, true );
362 Option::network_set( 'loggers', $loggers );
363 if ( $this->logger_modify( $uuid, $args, Option::network_get( 'logger_autostart' ) ) === $uuid ) {
364 return $uuid;
365 }
366 return '';
367 }
368
369 /**
370 * Filters records.
371 *
372 * @param array $records The records to filter.
373 * @param array $filters Optional. The filter to apply.
374 * @param string $index Optional. The starting index.
375 *
376 * @return array The filtered records.
377 * @since 2.0.0
378 */
379 public static function records_filter( $records, $filters = [], $index = '' ) {
380 $result = [];
381 foreach ( $records as $idx => $record ) {
382 foreach ( $filters as $key => $filter ) {
383 switch ( $key ) {
384 case 'level':
385 if ( EventTypes::$levels[ $record['level'] ] < EventTypes::$levels[ $filter ] ) {
386 continue 3;
387 }
388 break;
389 default:
390 if ( ! preg_match( $filter, $record[ $key ] ) ) {
391 continue 3;
392 }
393 }
394 }
395 $result[ $idx ] = $record;
396 }
397 if ( '' !== $index ) {
398 $tmp = [];
399 foreach ( $result as $key => $record ) {
400 if ( 0 < strcmp( $key, $index ) ) {
401 $tmp[ $key ] = $record;
402 }
403 }
404 $result = $tmp;
405 }
406 uksort( $result, 'strcmp' );
407 return $result;
408 }
409
410 /**
411 * Format records.
412 *
413 * @param array $records The records to display.
414 * @param string $mode Optional. The displaying mode.
415 * @param integer $pad Optional. Line padding.
416 *
417 * @return array The ready to print records.
418 * @since 2.0.0
419 */
420 public static function records_format( $records, $mode = '', $pad = 160 ) {
421 $result = [];
422 $geoip = new GeoIP();
423 foreach ( $records as $idx => $record ) {
424 $timestamp = '[' . Date::get_date_from_mysql_utc( $record['timestamp'], Timezone::network_get()->getName(), 'Y-m-d H:i:s' ) . ']';
425 $channel_level = strtoupper( str_pad( $record['channel'], 6 ) ) . ' ' . strtoupper( str_pad( $record['level'], 9 ) );
426 $component = $record['component'];
427 $message = trim( $record['message'] );
428 if ( 'unknown' !== $record['verb'] ) {
429 $verb = str_pad( '[' . strtoupper( $record['verb'] ) . ']', 9 );
430 } else {
431 $verb = str_pad( '[-]', 9 );
432 }
433
434 if ( $geoip->is_installed() ) {
435 $ip = EmojiFlag::get( $geoip->get_iso3166_alpha2( $record['remote_ip'] ) ) . ' ' . $record['remote_ip'];
436 } else {
437 $ip = $record['remote_ip'];
438 }
439 $url = $record['url'];
440 if ( 'unknown' === $record['classname'] ) {
441 $func = $record['function'] . '()';
442 } else {
443 $func = $record['classname'] . '::' . $record['function'] . '()';
444 }
445 $file = PHP::normalized_file_line( $record['file'], $record['line'] );
446 if ( Environment::is_wordpress_multisite() ) {
447 $sid = ' SID:' . str_pad( (string) $record['site_id'], 4, '0', STR_PAD_LEFT ) . ' ';
448 } else {
449 $sid = ' ';
450 }
451 $uid = ' UID:' . str_pad( (string) $record['user_id'], 6, '0', STR_PAD_LEFT ) . ' ';
452 $line = "$timestamp $channel_level$sid";
453 switch ( $mode ) {
454 case 'http':
455 if ( 'unknown' !== $record['verb'] ) {
456 $line = $line . "$verb $ip → $url";
457 } else {
458 $line = $line . "$verb $ip <No HTTP request>";
459 }
460 break;
461 case 'php':
462 $line = $line . "$func in $file";
463 break;
464 default:
465 $line = $line . "$uid$component: $message";
466 }
467 $line = preg_replace( '/[\x00-\x1F\x7F\xA0]/u', '', $line );
468 if ( $pad - 1 < strlen( $line ) ) {
469 $line = substr( $line, 0, $pad - 1 ) . '…';
470 }
471 $result[ $idx ] = [
472 'level' => strtolower( $record['level'] ),
473 'line' => decalog_mb_str_pad( $line, $pad ),
474 ];
475 }
476 return $result;
477 }
478
479 /**
480 * Displays records.
481 *
482 * @param array $records The records to display.
483 * @param string $mode Optional. The displaying mode.
484 * @param string $theme Optional. Colors scheme.
485 * @param integer $pad Optional. Line padding.
486 * @since 2.0.0
487 */
488 private function records_display( $records, $mode = '', $theme = 'standard', $pad = 160 ) {
489 if ( ! array_key_exists( $theme, $this->level_color ) ) {
490 $theme = 'standard';
491 }
492 foreach ( self::records_format( $records, $mode, $pad ) as $record ) {
493 \WP_CLI::line( \WP_CLI::colorize( $this->level_color[ $theme ][ strtolower( $record['level'] ) ] ) . $record['line'] . \WP_CLI::colorize( '%n' ) );
494 }
495 }
496
497 /**
498 * Get DecaLog details and operation modes.
499 *
500 * ## EXAMPLES
501 *
502 * wp log status
503 *
504 *
505 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
506 */
507 public function status( $args, $assoc_args ) {
508 $run = 0;
509 $list = 0;
510 foreach ( Option::network_get( 'loggers' ) as $key => $logger ) {
511 if ( $logger['running'] ) {
512 $run++;
513 }
514 }
515 if ( Option::network_get( 'autolisteners' ) ) {
516 $list = 'on all available listeners';
517 } else {
518 $listeners = ListenerFactory::$infos;
519 foreach ( $listeners as $listener ) {
520 if ( $listener['available'] && in_array( $listener['id'], Option::network_get( 'listeners' ), true ) ) {
521 $list++;
522 }
523 }
524 if ( 0 === $list ) {
525 $list = 'on no listener';
526 } elseif ( 1 === $list ) {
527 $list = 'on 1 listener';
528 } else {
529 $list = sprintf( 'on %d listeners', $list );
530 }
531 }
532 if ( 0 === $run ) {
533 $run = '';
534 $list = '';
535 } elseif ( 1 === $run ) {
536 $run = '1 logger';
537 } else {
538 $run = sprintf( '%d loggers', $run );
539 }
540 $pvt = Environment::plugin_version_text();
541 if ( class_exists( '\DecaLog\Engine' ) ) {
542 $pvt = \DecaLog\Engine::getVersionString();
543 }
544 \WP_CLI::line( sprintf( '%s running %s %s.', $pvt, $run, $list ) );
545 if ( Option::network_get( 'earlyloading' ) ) {
546 \WP_CLI::line( 'Early-Loading: enabled.' );
547 } else {
548 \WP_CLI::line( 'Early-Loading: disabled.' );
549 }
550 if ( Option::network_get( 'logger_autostart' ) ) {
551 \WP_CLI::line( 'Auto-Start: enabled.' );
552 } else {
553 \WP_CLI::line( 'Auto-Start: disabled.' );
554 }
555 if ( Autolog::is_enabled() ) {
556 \WP_CLI::line( 'Auto-Logging: enabled.' );
557 } else {
558 \WP_CLI::line( 'Auto-Logging: disabled.' );
559 }
560 if ( Option::network_get( 'metrics_authent' ) ) {
561 \WP_CLI::line( 'Endpoint authentication: enabled.' );
562 } else {
563 \WP_CLI::line( 'Endpoint authentication: disabled.' );
564 }
565 $geo = new GeoIP();
566 if ( $geo->is_installed() ) {
567 \WP_CLI::line( 'IP information support: yes (' . $geo->get_full_name() . ').' );
568 } else {
569 \WP_CLI::line( 'IP information support: no.' );
570 }
571 if ( class_exists( 'PODeviceDetector\API\Device' ) ) {
572 \WP_CLI::line( 'Device detection support: yes (Device Detector v' . PODD_VERSION . ').' );
573 } else {
574 \WP_CLI::line( 'Device detection support: no.' );
575 }
576 if ( SharedMemory::$available ) {
577 \WP_CLI::line( 'Shared memory support: yes (shmop v' . phpversion( 'shmop' ) . ').' );
578 } else {
579 \WP_CLI::line( 'Shared memory support: no.' );
580 }
581 }
582
583 /**
584 * Get information on logger types.
585 *
586 * ## OPTIONS
587 *
588 * <list|describe>
589 * : The action to take.
590 * ---
591 * options:
592 * - list
593 * - describe
594 * ---
595 *
596 * [<logger_type>]
597 * : The type of the logger to describe. Can be used to filter the list output too.
598 *
599 * [--format=<format>]
600 * : Allows overriding the output of the command when listing types.
601 * ---
602 * default: table
603 * options:
604 * - table
605 * - json
606 * - csv
607 * - yaml
608 * - ids
609 * - count
610 * ---
611 *
612 * [--stdout]
613 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
614 *
615 * ## EXAMPLES
616 *
617 * Lists available types:
618 * + wp log type list
619 * + wp log type list --format=json
620 *
621 * Details the WordpressHandler logger type:
622 * + wp log type describe WordpressHandler
623 *
624 *
625 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
626 *
627 */
628 public function type( $args, $assoc_args ) {
629 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
630 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
631 $action = $args[0] ?? 'list';
632 $uuid = $args[1] ?? '';
633 $handler_types = new HandlerTypes();
634 $handlers = [];
635 foreach ( $handler_types->get_all() as $key => $handler ) {
636 if ( 'system' !== $handler['class'] && ( '' === $uuid || $handler['id'] === $uuid ) ) {
637 $handler['type'] = $handler['id'];
638 $handlers[ strtolower( $handler['id'] ) ] = $handler;
639 }
640 }
641 uasort(
642 $handlers,
643 function ( $a, $b ) {
644 return strcmp( strtolower( $a['name'] ), strtolower( $b['name'] ) );
645 }
646 );
647 $uuid = '';
648 if ( isset( $args[1] ) ) {
649 $uuid = strtolower( $args[1] );
650 if ( ! array_key_exists( $uuid, $handlers ) && 'list' !== $action ) {
651 $uuid = '';
652 }
653 }
654 if ( 'list' !== $action && '' === $uuid ) {
655 $this->error( 1, $stdout );
656 }
657 switch ( $action ) {
658 case 'list':
659 $details = [];
660 foreach ( $handlers as $key => $handler ) {
661 $item = [];
662 foreach ( $handler as $i => $h ) {
663 if ( in_array( $i, [ 'type', 'class', 'name', 'version' ], true ) ) {
664 $item[ $i ] = $h;
665 }
666 }
667 $details[ $handler['type'] ] = $item;
668 }
669 if ( 'ids' === $format ) {
670 $this->write_ids( $handlers, 'type' );
671 } elseif ( 'yaml' === $format ) {
672 $details = Spyc::YAMLDump( $details, true, true, true );
673 $this->line( $details, $details, $stdout );
674 } elseif ( 'json' === $format ) {
675 $details = wp_json_encode( $details );
676 $this->line( $details, $details, $stdout );
677 } else {
678 \WP_CLI\Utils\format_items( $format, $details, [ 'type', 'class', 'name', 'version' ] );
679 }
680 break;
681 case 'describe':
682 $example = [];
683 $handler = $handlers[ $uuid ];
684 \WP_CLI::line( '' );
685 \WP_CLI::line( \WP_CLI::colorize( '%8' . $handler['name'] . ' - ' . $handler['id'] . '%n' ) );
686 \WP_CLI::line( $handler['help'] );
687 \WP_CLI::line( '' );
688 if ( 'metrics' !== $handler['class'] && 'tracing' !== $handler['class'] ) {
689 \WP_CLI::line( \WP_CLI::colorize( '%UMinimal Level%n' ) );
690 \WP_CLI::line( '' );
691 \WP_CLI::line( ' ' . strtolower( Log::level_name( $handler['minimal'] ) ) );
692 \WP_CLI::line( '' );
693 }
694 \WP_CLI::line( \WP_CLI::colorize( '%UParameters%n' ) );
695 \WP_CLI::line( '' );
696 $param = ' * ';
697 $elem = ' - ';
698 $list = ' ';
699 \WP_CLI::line( $param . 'Name - Used only in admin dashboard.' );
700 \WP_CLI::line( $elem . 'field name: name' );
701 \WP_CLI::line( $elem . 'field type: string' );
702 \WP_CLI::line( $elem . 'default value: "New Logger"' );
703 \WP_CLI::line( '' );
704 if ( 'metrics' !== $handler['class'] && 'tracing' !== $handler['class'] ) {
705 \WP_CLI::line( $param . 'Minimal level - Minimal reported level.' );
706 \WP_CLI::line( $elem . 'field name: level' );
707 \WP_CLI::line( $elem . 'field type: string' );
708 \WP_CLI::line( $elem . 'default value: "' . strtolower( Log::level_name( $handler['minimal'] ) ) . '"' );
709 \WP_CLI::line( $elem . 'available values:' );
710 foreach ( Log::get_levels( EventTypes::$levels[ strtolower( Log::level_name( $handler['minimal'] ) ) ] ) as $level ) {
711 \WP_CLI::line( $list . '"' . strtolower( $level[1] ) . '": ' . $level[2] );
712 }
713 \WP_CLI::line( '' );
714 }
715 foreach ( $handler['configuration'] as $key => $conf ) {
716 if ( ! $conf['show'] || ! $conf['control']['enabled'] ) {
717 continue;
718 }
719 \WP_CLI::line( $param . $conf['name'] . ' - ' . $conf['help'] );
720 \WP_CLI::line( $elem . 'field name: ' . $key );
721 \WP_CLI::line( $elem . 'field type: ' . $conf['type'] );
722 switch ( $conf['control']['type'] ) {
723 case 'field_input_integer':
724 \WP_CLI::line( $elem . 'default value: ' . $conf['default'] );
725 \WP_CLI::line( $elem . 'range: [' . $conf['control']['min'] . '-' . $conf['control']['max'] . ']' );
726 $example[] = '"' . $key . '": ' . $conf['default'];
727 break;
728 case 'field_checkbox':
729 \WP_CLI::line( $elem . 'default value: ' . ( $conf['default'] ? 'true' : 'false' ) );
730 $example[] = '"' . $key . '": ' . ( $conf['default'] ? 'true' : 'false' );
731 break;
732 case 'field_input_text':
733 \WP_CLI::line( $elem . 'default value: "' . $conf['default'] . '"' );
734 $example[] = '"' . $key . '": "' . $conf['default'] . '"';
735 break;
736 case 'field_select':
737 switch ( $conf['control']['cast'] ) {
738 case 'integer':
739 \WP_CLI::line( $elem . 'default value: ' . $conf['default'] );
740 $example[] = '"' . $key . '": ' . $conf['default'];
741 break;
742 case 'string':
743 \WP_CLI::line( $elem . 'default value: "' . $conf['default'] . '"' );
744 $example[] = '"' . $key . '": "' . $conf['default'] . '"';
745 break;
746 }
747 \WP_CLI::line( $elem . 'available values:' );
748 foreach ( $conf['control']['list'] as $point ) {
749 switch ( $conf['control']['cast'] ) {
750 case 'integer':
751 \WP_CLI::line( $list . $point[0] . ': ' . $point[1] );
752 break;
753 case 'string':
754 \WP_CLI::line( $list . '"' . $point[0] . '": ' . $point[1] );
755 break;
756 }
757 }
758 break;
759 }
760 \WP_CLI::line( '' );
761 }
762 if ( 'metrics' !== $handler['class'] ) {
763 \WP_CLI::line( $param . 'IP obfuscation - Log fields will contain hashes instead of real IPs.' );
764 \WP_CLI::line( $elem . 'field name: obfuscation' );
765 \WP_CLI::line( $elem . 'field type: boolean' );
766 \WP_CLI::line( $elem . 'default value: false' );
767 \WP_CLI::line( '' );
768 \WP_CLI::line( $param . 'User pseudonymization - Log fields will contain hashes instead of user IDs & names.' );
769 \WP_CLI::line( $elem . 'field name: pseudonymization' );
770 \WP_CLI::line( $elem . 'field type: boolean' );
771 \WP_CLI::line( $elem . 'default value: false' );
772 \WP_CLI::line( '' );
773 \WP_CLI::line( $param . 'Reported details: WordPress - Allows to log site, user and remote IP of the current request.' );
774 \WP_CLI::line( $elem . 'field name: proc_wp' );
775 \WP_CLI::line( $elem . 'field type: boolean' );
776 \WP_CLI::line( $elem . 'default value: true' );
777 \WP_CLI::line( '' );
778 \WP_CLI::line( $param . 'Reported details: HTTP request - Allows to log url, method, referrer and remote IP of the current web request.' );
779 \WP_CLI::line( $elem . 'field name: proc_http' );
780 \WP_CLI::line( $elem . 'field type: boolean' );
781 \WP_CLI::line( $elem . 'default value: true' );
782 \WP_CLI::line( '' );
783 \WP_CLI::line( $param . 'Reported details: PHP introspection - Allows to log line, file, class and function generating the event.' );
784 \WP_CLI::line( $elem . 'field name: proc_php' );
785 \WP_CLI::line( $elem . 'field type: boolean' );
786 \WP_CLI::line( $elem . 'default value: true' );
787 \WP_CLI::line( '' );
788 if ( 'tracing' !== $handler['class'] ) {
789 \WP_CLI::line( $param . 'Reported details: Backtrace - Allows to log the full PHP and WordPress call stack.' );
790 \WP_CLI::line( $elem . 'field name: proc_trace' );
791 \WP_CLI::line( $elem . 'field type: boolean' );
792 \WP_CLI::line( $elem . 'default value: false' );
793 \WP_CLI::line( '' );
794 }
795 }
796 \WP_CLI::line( \WP_CLI::colorize( '%UExample%n' ) );
797 \WP_CLI::line( '' );
798 \WP_CLI::line( ' {' . implode( ', ', $example ) . '}' );
799 \WP_CLI::line( '' );
800 break;
801 }
802
803 }
804
805 /**
806 * Manage Decalog loggers.
807 *
808 * ## OPTIONS
809 *
810 * <list|start|pause|clean|purge|remove|add|set>
811 * : The action to take.
812 * ---
813 * options:
814 * - list
815 * - start
816 * - pause
817 * - clean
818 * - purge
819 * - remove
820 * - add
821 * - set
822 * ---
823 *
824 * [<uuid_or_type>]
825 * : The uuid of the logger to perform an action on or the type of the logger to add. Can be used to filter the list output too.
826 *
827 * [--settings=<settings>]
828 * : The settings needed by "add" and "modify" actions.
829 * MUST be a string containing a json configuration.
830 * ---
831 * default: '{}'
832 * example: '{"host": "syslog.collection.eu.sumologic.com", "timeout": 800, "ident": "DecaLog", "format": 1}'
833 * ---
834 *
835 * [--detail=<detail>]
836 * : The details of the output when listing loggers.
837 * ---
838 * default: short
839 * options:
840 * - short
841 * - full
842 * ---
843 *
844 * [--format=<format>]
845 * : Allows overriding the output of the command when listing loggers. Note if json or yaml is chosen: full metadata is outputted too.
846 * ---
847 * default: table
848 * options:
849 * - table
850 * - json
851 * - csv
852 * - yaml
853 * - ids
854 * - count
855 * ---
856 *
857 * [--yes]
858 * : Answer yes to the confirmation message, if any.
859 *
860 * [--stdout]
861 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
862 *
863 * ## EXAMPLES
864 *
865 * Lists configured loggers:
866 * + wp log logger list
867 * + wp log logger list --detail=full
868 * + wp log logger list --format=json
869 *
870 * Starts a logger:
871 * + wp log logger start 37cf1c00-d67d-4e7d-9518-e579f01407a7
872 *
873 * Pauses a logger:
874 * + wp log logger pause 37cf1c00-d67d-4e7d-9518-e579f01407a7
875 *
876 * Deletes old records of a logger:
877 * + wp log logger clean 37cf1c00-d67d-4e7d-9518-e579f01407a7
878 *
879 * Deletes all records of a logger:
880 * + wp log logger purge 37cf1c00-d67d-4e7d-9518-e579f01407a7
881 * + wp log logger purge 37cf1c00-d67d-4e7d-9518-e579f01407a7 --yes
882 *
883 * Permanently deletes a logger:
884 * + wp log logger remove 37cf1c00-d67d-4e7d-9518-e579f01407a7
885 * + wp log logger remove 37cf1c00-d67d-4e7d-9518-e579f01407a7 --yes
886 *
887 * Adds a new logger:
888 * + wp log logger add WordpressHandler --settings='{"rotate": 8000, "purge": 5, "level":"warning", "proc_wp": true}'
889 *
890 * Change the settings of a logger
891 * + wp log logger set 37cf1c00-d67d-4e7d-9518-e579f01407a7 --settings='{"proc_trace": false, "level":"warning"}'
892 *
893 *
894 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
895 *
896 */
897 public function logger( $args, $assoc_args ) {
898 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
899 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
900 $detail = \WP_CLI\Utils\get_flag_value( $assoc_args, 'detail', 'short' );
901 $uuid = '';
902 $type = '';
903 $ilog = Log::bootstrap( 'plugin', DECALOG_PRODUCT_SHORTNAME, DECALOG_VERSION );
904 $action = $args[0] ?? 'list';
905 $loggers_list = Option::network_get( 'loggers' );
906 if ( isset( $args[1] ) ) {
907 $uuid = $args[1];
908 if ( 'add' === $action || 'list' === $action ) {
909 $handler_types = new HandlerTypes();
910 $t = '';
911 foreach ( $handler_types->get_all() as $handler ) {
912 if ( 'system' !== $handler['class'] && strtolower( $uuid ) === strtolower( $handler['id'] ) ) {
913 $t = $uuid;
914 }
915 if ( 'system' === $handler['class'] && strtolower( $uuid ) === strtolower( $handler['id'] ) ) {
916 $t = 'system';
917 }
918 }
919 $type = $t;
920 }
921 if ( 'add' !== $action ) {
922 if ( ! array_key_exists( $uuid, $loggers_list ) ) {
923 $uuid = '';
924 } else {
925 $handler_types = new HandlerTypes();
926 foreach ( $handler_types->get_all() as $handler ) {
927 if ( 'system' === $handler['class'] && $loggers_list[ $uuid ]['handler'] === $handler['id'] ) {
928 $uuid = 'system';
929 }
930 }
931 }
932 }
933 }
934 if ( 'add' === $action && '' === $type ) {
935 $this->error( 1, $stdout );
936 } elseif ( 'system' === $uuid ) {
937 $this->error( 3, $stdout );
938 } elseif ( 'list' !== $action && '' === $uuid ) {
939 $this->error( 2, $stdout );
940 }
941 switch ( $action ) {
942 case 'list':
943 $handler_types = new HandlerTypes();
944 $processor_types = new ProcessorTypes();
945 $loggers = [];
946 foreach ( $loggers_list as $key => $logger ) {
947 $handler = $handler_types->get( $logger['handler'] );
948 $logger['type'] = $handler['name'];
949 $logger['uuid'] = $key;
950 $logger['level'] = strtolower( Log::level_name( $logger['level'] ) );
951 $logger['running'] = $logger['running'] ? 'yes' : 'no';
952 $list = [ 'Standard' ];
953 foreach ( $logger['processors'] as $processor ) {
954 $list[] = $processor_types->get( $processor )['name'];
955 }
956 $logger['processors'] = implode( ', ', $list );
957 if ( ( '' === $uuid && '' === $type ) || $key === $uuid || strtolower( $logger['handler'] ) === strtolower( $type ) ) {
958 $loggers[ $key ] = $logger;
959 }
960 }
961 usort(
962 $loggers,
963 function ( $a, $b ) {
964 return strcmp( strtolower( $a['name'] ), strtolower( $b['name'] ) );
965 }
966 );
967 if ( 'full' === $detail ) {
968 $detail = [ 'uuid', 'type', 'name', 'running', 'level', 'processors' ];
969 } else {
970 $detail = [ 'uuid', 'type', 'name', 'running' ];
971 }
972 if ( 'ids' === $format ) {
973 $this->write_ids( $loggers, 'uuid' );
974 } elseif ( 'yaml' === $format ) {
975 $details = Spyc::YAMLDump( $loggers_list, true, true, true );
976 $this->line( $details, $details, $stdout );
977 } elseif ( 'json' === $format ) {
978 $details = wp_json_encode( $loggers_list );
979 $this->line( $details, $details, $stdout );
980 } else {
981 \WP_CLI\Utils\format_items( $format, $loggers, $detail );
982 }
983 break;
984 case 'start':
985 if ( $loggers_list[ $uuid ]['running'] ) {
986 $this->warning( sprintf( 'The logger %s is already running.', $uuid ), $uuid, $stdout );
987 } else {
988 $loggers_list[ $uuid ]['running'] = true;
989 Option::network_set( 'loggers', $loggers_list );
990 $ilog->info( sprintf( 'Logger "%s" has started.', $loggers_list[ $uuid ]['name'] ) );
991 $this->success( sprintf( 'Logger %s is now running.', $uuid ), $uuid, $stdout );
992 }
993 break;
994 case 'pause':
995 if ( ! $loggers_list[ $uuid ]['running'] ) {
996 $this->warning( sprintf( 'The logger %s is already paused.', $uuid ), $uuid, $stdout );
997 } else {
998 $loggers_list[ $uuid ]['running'] = false;
999 $ilog->info( sprintf( 'Logger "%s" has been paused.', $loggers_list[ $uuid ]['name'] ) );
1000 Option::network_set( 'loggers', $loggers_list );
1001 $this->success( sprintf( 'Logger %s is now paused.', $uuid ), $uuid, $stdout );
1002 }
1003 break;
1004 case 'purge':
1005 $loggers_list[ $uuid ]['uuid'] = $uuid;
1006 if ( 'WordpressHandler' !== $loggers_list[ $uuid ]['handler'] ) {
1007 $this->warning( sprintf( 'The logger %s can\'t be purged.', $uuid ), $uuid, $stdout );
1008 } else {
1009 \WP_CLI::confirm( sprintf( 'Are you sure you want to purge logger %s?', $uuid ), $assoc_args );
1010 $factory = new LoggerFactory();
1011 $factory->purge( $loggers_list[ $uuid ] );
1012 $ilog->notice( sprintf( 'Logger "%s" has been purged.', $loggers_list[ $uuid ]['name'] ) );
1013 $this->success( sprintf( 'Logger %s successfully purged.', $uuid ), $uuid, $stdout );
1014 }
1015 break;
1016 case 'clean':
1017 $loggers_list[ $uuid ]['uuid'] = $uuid;
1018 if ( 'WordpressHandler' !== $loggers_list[ $uuid ]['handler'] ) {
1019 $this->warning( sprintf( 'The logger %s can\'t be cleaned.', $uuid ), $uuid, $stdout );
1020 } else {
1021 $factory = new LoggerFactory();
1022 $count = $factory->clean( $loggers_list[ $uuid ] );
1023 $this->log( sprintf( '%d record(s) deleted.', $count ), $stdout );
1024 $this->success( sprintf( 'Logger %s successfully cleaned.', $uuid ), $uuid, $stdout );
1025 }
1026 break;
1027 case 'remove':
1028 $loggers_list[ $uuid ]['uuid'] = $uuid;
1029 \WP_CLI::confirm( sprintf( 'Are you sure you want to remove logger %s?', $uuid ), $assoc_args );
1030 $factory = new LoggerFactory();
1031 $factory->destroy( $loggers_list[ $uuid ] );
1032 $ilog->notice( sprintf( 'Logger "%s" has been removed.', $loggers_list[ $uuid ]['name'] ) );
1033 unset( $loggers_list[ $uuid ] );
1034 Option::network_set( 'loggers', $loggers_list );
1035 $this->success( sprintf( 'logger %s successfully removed.', $uuid ), $uuid, $stdout );
1036 break;
1037 case 'add':
1038 $result = $this->logger_add( $type, $assoc_args );
1039 if ( '' === $result ) {
1040 $ilog->error( 'Unable to add a logger.', 1 );
1041 $this->error( 4, $stdout );
1042 } else {
1043 $loggers_list = Option::network_get( 'loggers' );
1044 $ilog->notice( sprintf( 'Logger "%s" has been saved.', $loggers_list[ $result ]['name'] ) );
1045 $this->success( sprintf( 'Logger %s successfully created.', $result ), $result, $stdout );
1046 }
1047 break;
1048 case 'set':
1049 $result = $this->logger_modify( $uuid, $assoc_args );
1050 if ( '' === $result ) {
1051 $ilog->error( 'Unable to modify a logger.', 1 );
1052 $this->error( 5, $stdout );
1053 } else {
1054 $loggers_list = Option::network_get( 'loggers' );
1055 $ilog->notice( sprintf( 'Logger "%s" has been saved.', $loggers_list[ $result ]['name'] ) );
1056 $this->success( sprintf( 'Logger %s successfully saved.', $result ), $result, $stdout );
1057 }
1058 break;
1059 }
1060 }
1061
1062 /**
1063 * Manage Decalog listeners.
1064 *
1065 * ## OPTIONS
1066 *
1067 * <list|enable|disable|auto-on|auto-off>
1068 * : The action to take.
1069 * ---
1070 * default: list
1071 * options:
1072 * - list
1073 * - enable
1074 * - disable
1075 * - auto-on
1076 * - auto-off
1077 * ---
1078 *
1079 * [<listener_id>]
1080 * : The id of the listener to perform an action on. Can be used to filter the list output too.
1081 *
1082 * [--detail=<detail>]
1083 * : The details of the output when listing listeners. Note if json or yaml is chosen: full metadata is outputted too.
1084 * ---
1085 * default: short
1086 * options:
1087 * - short
1088 * - full
1089 * ---
1090 *
1091 * [--format=<format>]
1092 * : Allows overriding the output of the command when listing listeners.
1093 * ---
1094 * default: table
1095 * options:
1096 * - table
1097 * - json
1098 * - csv
1099 * - yaml
1100 * - ids
1101 * - count
1102 * ---
1103 *
1104 * [--yes]
1105 * : Answer yes to the confirmation message, if any.
1106 *
1107 * [--stdout]
1108 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1109 *
1110 * ## EXAMPLES
1111 *
1112 * Lists configured listeners:
1113 * + wp log listener list
1114 * + wp log listener list --detail=full
1115 * + wp log listener list --format=json
1116 *
1117 * Enables a listener:
1118 * + wp log listener enable wpdb
1119 *
1120 * Disables a listener:
1121 * wp log listener disable wpdb
1122 *
1123 * Activates auto-listening:
1124 * + wp log listener auto-on
1125 * + wp log listener auto-on --yes
1126 *
1127 * Deactivates auto-listening:
1128 * + wp log listener auto-off
1129 * + wp log listener auto-off --yes
1130 *
1131 *
1132 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1133 *
1134 */
1135 public function listener( $args, $assoc_args ) {
1136 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1137 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
1138 $detail = \WP_CLI\Utils\get_flag_value( $assoc_args, 'detail', 'short' );
1139 $activated = Option::network_get( 'listeners' );
1140 $listeners = [];
1141 $uuid = '';
1142 $ilog = Log::bootstrap( 'plugin', DECALOG_PRODUCT_SHORTNAME, DECALOG_VERSION );
1143 $action = $args[0] ?? 'list';
1144 if ( isset( $args[1] ) ) {
1145 $uuid = strtolower( $args[1] );
1146 }
1147 foreach ( ListenerFactory::$infos as $listener ) {
1148 if ( '' === $uuid || $listener['id'] === $uuid ) {
1149 $listener['enabled'] = Option::network_get( 'autolisteners' ) ? 'auto' : ( in_array( $listener['id'], $activated, true ) ? 'yes' : 'no' );
1150 $listener['available'] = $listener['available'] ? 'yes' : 'no';
1151 $listeners[ $listener['id'] ] = $listener;
1152 if ( 'yaml' === $format || 'json' === $format ) {
1153 unset( $listeners[ $listener['id'] ]['id'] );
1154 }
1155 }
1156 }
1157 uasort(
1158 $listeners,
1159 function ( $a, $b ) {
1160 return strcmp( strtolower( $a['name'] ), strtolower( $b['name'] ) );
1161 }
1162 );
1163 if ( ! array_key_exists( $uuid, $listeners ) && 'list' !== $action ) {
1164 $uuid = '';
1165 }
1166
1167 if ( 'list' !== $action && 'auto-on' !== $action && 'auto-off' !== $action && '' === $uuid ) {
1168 $this->error( 6, $stdout );
1169 }
1170 switch ( $action ) {
1171 case 'list':
1172 if ( 'full' === $detail ) {
1173 $detail = [ 'id', 'class', 'name', 'product', 'version', 'available', 'enabled', 'step' ];
1174 } else {
1175 $detail = [ 'id', 'name', 'available', 'enabled' ];
1176 }
1177 if ( 'ids' === $format ) {
1178 $this->write_ids( $listeners, 'id' );
1179 } elseif ( 'yaml' === $format ) {
1180 $details = Spyc::YAMLDump( $listeners, true, true, true );
1181 $this->line( $details, $details, $stdout );
1182 } elseif ( 'json' === $format ) {
1183 $details = wp_json_encode( $listeners );
1184 $this->line( $details, $details, $stdout );
1185 } else {
1186 \WP_CLI\Utils\format_items( $format, $listeners, $detail );
1187 }
1188 break;
1189 case 'enable':
1190 if ( in_array( $uuid, $activated, true ) ) {
1191 $this->warning( sprintf( 'The listener %s is already enabled.', $uuid ), $uuid, $stdout );
1192 } else {
1193 $activated[] = $uuid;
1194 Option::network_set( 'listeners', $activated );
1195 $ilog->info( 'Listeners settings updated.' );
1196 $this->success( sprintf( 'The listener %s is now enabled.', $uuid ), $uuid, $stdout );
1197 }
1198 break;
1199 case 'disable':
1200 if ( ! in_array( $uuid, $activated, true ) ) {
1201 $this->warning( sprintf( 'The listener %s is already disabled.', $uuid ), $uuid, $stdout );
1202 } else {
1203 $list = [];
1204 foreach ( $activated as $listener ) {
1205 if ( $listener !== $uuid ) {
1206 $list[] = $listener;
1207 }
1208 }
1209 Option::network_set( 'listeners', $list );
1210 $ilog->info( 'Listeners settings updated.' );
1211 $this->success( sprintf( 'The listener %s is now disabled.', $uuid ), $uuid, $stdout );
1212 }
1213 break;
1214 case 'auto-on':
1215 if ( Option::network_get( 'autolisteners' ) ) {
1216 $this->warning( 'Auto-listening is already activated.', '', $stdout );
1217 } else {
1218 \WP_CLI::confirm( 'Are you sure you want to activate auto-listening?', $assoc_args );
1219 Option::network_set( 'autolisteners', true );
1220 $ilog->info( 'Listeners settings updated.' );
1221 $this->success( 'Auto-listening is now activated.', '', $stdout );
1222 }
1223 break;
1224 case 'auto-off':
1225 if ( ! Option::network_get( 'autolisteners' ) ) {
1226 $this->warning( 'Auto-listening is already deactivated.', '', $stdout );
1227 } else {
1228 \WP_CLI::confirm( 'Are you sure you want to deactivate auto-listening?', $assoc_args );
1229 Option::network_set( 'autolisteners', false );
1230 $ilog->info( 'Listeners settings updated.' );
1231 $this->success( 'Auto-listening is now deactivated.', '', $stdout );
1232 }
1233 break;
1234 }
1235
1236 }
1237
1238 /**
1239 * Modify DecaLog main settings.
1240 *
1241 * ## OPTIONS
1242 *
1243 * <enable|disable>
1244 * : The action to take.
1245 *
1246 * <early-loading|auto-logging|auto-start|auth-endpoint|slow-query-warn|query-trace>
1247 * : The setting to change.
1248 *
1249 * [--yes]
1250 * : Answer yes to the confirmation message, if any.
1251 *
1252 * [--stdout]
1253 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1254 *
1255 * ## EXAMPLES
1256 *
1257 * wp log settings enable auto-logging
1258 * wp log settings disable early-loading --yes
1259 *
1260 *
1261 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1262 *
1263 */
1264 public function settings( $args, $assoc_args ) {
1265 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1266 $action = isset( $args[0] ) ? (string) $args[0] : '';
1267 $setting = isset( $args[1] ) ? (string) $args[1] : '';
1268 switch ( $action ) {
1269 case 'enable':
1270 switch ( $setting ) {
1271 case 'early-loading':
1272 Option::network_set( 'earlyloading', true );
1273 $this->success( 'Early-loading is now activated.', '', $stdout );
1274 break;
1275 case 'auto-start':
1276 Option::network_set( 'logger_autostart', true );
1277 $this->success( 'Auto-start is now activated.', '', $stdout );
1278 break;
1279 case 'auto-logging':
1280 Autolog::activate();
1281 $this->success( 'Auto-logging is now activated.', '', $stdout );
1282 break;
1283 case 'auth-endpoint':
1284 Option::network_set( 'metrics_authent', true );
1285 $this->success( 'Endpoints authentication is now activated.', '', $stdout );
1286 break;
1287 case 'slow-query-warn':
1288 Option::network_set( 'slow_query_warn', true );
1289 $this->success( 'Slow-query warning is now activated.', '', $stdout );
1290 break;
1291 case 'trace-query':
1292 Option::network_set( 'trace_query', true );
1293 $this->success( 'Query tracing is now activated.', '', $stdout );
1294 break;
1295 default:
1296 $this->error( 7, $stdout );
1297 }
1298 break;
1299 case 'disable':
1300 switch ( $setting ) {
1301 case 'early-loading':
1302 \WP_CLI::confirm( 'Are you sure you want to deactivate early-loading?', $assoc_args );
1303 Option::network_set( 'earlyloading', false );
1304 $this->success( 'Early-loading is now deactivated.', '', $stdout );
1305 break;
1306 case 'auto-start':
1307 \WP_CLI::confirm( 'Are you sure you want to deactivate auto-start?', $assoc_args );
1308 Option::network_set( 'logger_autostart', false );
1309 $this->success( 'Auto-start is now deactivated.', '', $stdout );
1310 break;
1311 case 'auto-logging':
1312 \WP_CLI::confirm( 'Are you sure you want to deactivate auto-logging?', $assoc_args );
1313 Autolog::deactivate();
1314 $this->success( 'Auto-logging is now deactivated.', '', $stdout );
1315 break;
1316 case 'auth-endpoint':
1317 \WP_CLI::confirm( 'Are you sure you want to deactivate endpoint authentication?', $assoc_args );
1318 Option::network_set( 'metrics_authent', false );
1319 $this->success( 'Endpoints authentication is now deactivated.', '', $stdout );
1320 break;
1321 case 'slow-query-warn':
1322 \WP_CLI::confirm( 'Are you sure you want to deactivate slow-query warning?', $assoc_args );
1323 Option::network_set( 'slow_query_warn', false );
1324 $this->success( 'Slow-query warning is now deactivated.', '', $stdout );
1325 break;
1326 case 'trace-query':
1327 \WP_CLI::confirm( 'Are you sure you want to deactivate query tracing?', $assoc_args );
1328 Option::network_set( 'trace_query', false );
1329 $this->success( 'Query tracing is now deactivated.', '', $stdout );
1330 break;
1331 default:
1332 $this->error( 7, $stdout );
1333 }
1334 break;
1335 default:
1336 $this->error( 8, $stdout );
1337 }
1338 }
1339
1340 /**
1341 * Send an event to all running loggers.
1342 *
1343 * ## OPTIONS
1344 *
1345 * <info|notice|warning|error|critical|alert>
1346 * : The level of the event.
1347 *
1348 * <message>
1349 * : The message.
1350 *
1351 * [--code=<code>]
1352 * : The code of the event. Must be a positive integer. Default is 0.
1353 *
1354 * [--stdout]
1355 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1356 *
1357 * ## EXAMPLES
1358 *
1359 * wp log send info 'This is an informational message'
1360 * wp log send warning 'Page not found' --code=404
1361 *
1362 *
1363 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1364 *
1365 */
1366 public function send( $args, $assoc_args ) {
1367 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1368 $level = isset( $args[0] ) ? strtolower( $args[0] ) : '';
1369 $message = isset( $args[1] ) ? (string) $args[1] : '';
1370 $code = isset( $assoc_args['code'] ) ? (int) $assoc_args['code'] : 0;
1371 if ( 0 > $code ) {
1372 $code = 0;
1373 }
1374 if ( ! in_array( $level, [ 'info', 'notice', 'warning', 'error', 'critical', 'alert' ], true ) ) {
1375 $this->error( 10, $stdout );
1376 }
1377 $logger = Log::bootstrap( 'plugin', DECALOG_PRODUCT_SHORTNAME, DECALOG_VERSION );
1378 $logger->log( $level, $message, $code );
1379 $this->success( 'Event triggered and sent.', 'OK', $stdout );
1380 }
1381
1382 /**
1383 * Get information on exit codes.
1384 *
1385 * ## OPTIONS
1386 *
1387 * <list>
1388 * : The action to take.
1389 * ---
1390 * options:
1391 * - list
1392 * ---
1393 *
1394 * [--format=<format>]
1395 * : Allows overriding the output of the command when listing exit codes.
1396 * ---
1397 * default: table
1398 * options:
1399 * - table
1400 * - json
1401 * - csv
1402 * - yaml
1403 * - ids
1404 * - count
1405 * ---
1406 *
1407 * [--stdout]
1408 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1409 *
1410 * ## EXAMPLES
1411 *
1412 * Lists available exit codes:
1413 * + wp log exitcode list
1414 * + wp log exitcode list --format=json
1415 *
1416 *
1417 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1418 *
1419 */
1420 public function exitcode( $args, $assoc_args ) {
1421 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1422 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
1423 $action = $args[0] ?? 'list';
1424 $codes = [];
1425 foreach ( $this->exit_codes as $key => $msg ) {
1426 $codes[ $key ] = [
1427 'code' => $key,
1428 'meaning' => ucfirst( $msg ),
1429 ];
1430 }
1431 switch ( $action ) {
1432 case 'list':
1433 if ( 'ids' === $format ) {
1434 $this->write_ids( $codes );
1435 } else {
1436 \WP_CLI\Utils\format_items( $format, $codes, [ 'code', 'meaning' ] );
1437 }
1438 break;
1439 }
1440 }
1441
1442 /**
1443 * Get information on collated metrics.
1444 *
1445 * ## OPTIONS
1446 *
1447 * <list|dump|get>
1448 * : The action to take.
1449 * ---
1450 * options:
1451 * - list
1452 * - dump
1453 * - get
1454 * ---
1455 *
1456 * [<metrics_id>]
1457 * : The id of the metric to perform an action on. Can be used to filter the list or dump output too.
1458 *
1459 * [--format=<format>]
1460 * : Allows overriding the output of the command when listing or dumping metrics.
1461 * ---
1462 * default: table
1463 * options:
1464 * - table
1465 * - json
1466 * - csv
1467 * - yaml
1468 * - ids
1469 * - count
1470 * ---
1471 *
1472 * [--detail=<detail>]
1473 * : The details of the output when listing metrics. Note if json or yaml is chosen: full metadata is outputted too.
1474 * ---
1475 * default: short
1476 * options:
1477 * - short
1478 * - full
1479 * ---
1480 *
1481 * [--stdout]
1482 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1483 *
1484 * ## EXAMPLES
1485 *
1486 * Lists currently collated metrics:
1487 * + wp log metrics list
1488 * + wp log metrics list --format=json
1489 *
1490 * Dumps current metrics value:
1491 * + wp log metrics dump
1492 * + wp log metrics dump --format=yaml
1493 *
1494 * Get the value of a specific metrics to use in a script
1495 * + wp log metrics get wordpress_php_php_execution_latency --stdout
1496 *
1497 *
1498 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1499 *
1500 */
1501 public function metrics( $args, $assoc_args ) {
1502 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1503 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
1504 $detail = \WP_CLI\Utils\get_flag_value( $assoc_args, 'detail', 'short' );
1505 $action = $args[0] ?? 'list';
1506 $uuid = $args[1] ?? '';
1507 $list = [];
1508 foreach ( DMonitor::get_metrics_definition() as $key => $metrics ) {
1509 if ( '' === $uuid || $key === $uuid ) {
1510 unset( $metrics['name'] );
1511 if ( 'yaml' !== $format && 'json' !== $format ) {
1512 $metrics['id'] = $key;
1513 }
1514 $list[ $key ] = $metrics;
1515 }
1516 }
1517 switch ( $action ) {
1518 case 'list':
1519 if ( 0 === count( $list ) ) {
1520 $this->error( 9, $stdout );
1521 }
1522 if ( 'full' === $detail ) {
1523 $detail = [ 'id', 'class', 'profile', 'type', 'source', 'version', 'description' ];
1524 } else {
1525 $detail = [ 'id', 'description' ];
1526 }
1527 if ( 'ids' === $format ) {
1528 $this->write_ids( $list );
1529 } elseif ( 'yaml' === $format ) {
1530 $details = Spyc::YAMLDump( $list, true, true, true );
1531 $this->line( $details, $details, $stdout );
1532 } elseif ( 'json' === $format ) {
1533 $details = wp_json_encode( $list );
1534 $this->line( $details, $details, $stdout );
1535 } else {
1536 \WP_CLI\Utils\format_items( $format, $list, $detail );
1537 }
1538 break;
1539 case 'dump':
1540 $monitor = new DMonitor( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
1541 $monitor->before_close();
1542 ListenerFactory::force_monitoring_close();
1543 $production = $monitor->prod_registry()->getMetricFamilySamples();
1544 $development = $monitor->dev_registry()->getMetricFamilySamples();
1545 $result = [];
1546 if ( 'full' === $detail ) {
1547 $detail = [ 'id', 'type', 'key', 'value' ];
1548 } else {
1549 $detail = [ 'id', 'type', 'key', 'value' ];
1550 }
1551 foreach ( array_merge( $production, $development ) as $metrics ) {
1552 if ( '' === $uuid || $metrics->getName() === $uuid ) {
1553 switch ( $metrics->getType() ) {
1554 case 'gauge':
1555 case 'counter':
1556 $s = [];
1557 $s['id'] = $metrics->getName();
1558 $s['type'] = $metrics->getType();
1559 $s['key'] = 'current';
1560 $samples = $metrics->getSamples();
1561 if ( 1 === count( $samples ) ) {
1562 $s['value'] = (float) $samples[0]->getValue();
1563 $result[ $metrics->getName() ] = $s;
1564 }
1565 break;
1566 case 'histogram':
1567 foreach ( $metrics->getSamples() as $sample ) {
1568 $s = [];
1569 $s['id'] = $metrics->getName();
1570 $s['type'] = $metrics->getType();
1571 $name = $sample->getName();
1572 if ( strlen( $name ) - 4 === strpos( $name, '_sum' ) ) {
1573 $s['key'] = 'sum';
1574 $s['value'] = (float) $sample->getValue();
1575 }
1576 if ( strlen( $name ) - 6 === strpos( $name, '_count' ) ) {
1577 $s['key'] = 'count';
1578 $s['value'] = (float) $sample->getValue();
1579 }
1580 if ( strlen( $name ) - 7 === strpos( $name, '_bucket' ) ) {
1581 $labels = $sample->getLabelValues();
1582 $s['key'] = 'bucket - ' . end( $labels );
1583 $s['value'] = (float) $sample->getValue();
1584 $name .= '_' . end( $labels );
1585 }
1586 $result[ $name ] = $s;
1587 }
1588 break;
1589 }
1590 }
1591 }
1592 if ( 0 === count( $result ) ) {
1593 $this->error( 9, $stdout );
1594 }
1595 if ( 'ids' === $format ) {
1596 $this->write_ids( $result );
1597 } elseif ( 'yaml' === $format ) {
1598 $details = Spyc::YAMLDump( $result, true, true, true );
1599 $this->line( $details, $details, $stdout );
1600 } elseif ( 'json' === $format ) {
1601 $details = wp_json_encode( $result );
1602 $this->line( $details, $details, $stdout );
1603 } else {
1604 \WP_CLI\Utils\format_items( $format, $result, $detail );
1605 }
1606 break;
1607 case 'get':
1608 $monitor = new DMonitor( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
1609 $monitor->before_close();
1610 ListenerFactory::force_monitoring_close();
1611 $production = $monitor->prod_registry()->getMetricFamilySamples();
1612 $development = $monitor->dev_registry()->getMetricFamilySamples();
1613 foreach ( array_merge( $production, $development ) as $metrics ) {
1614 if ( $metrics->getName() === $uuid ) {
1615 switch ( $metrics->getType() ) {
1616 case 'gauge':
1617 case 'counter':
1618 $samples = $metrics->getSamples();
1619 if ( 1 === count( $samples ) ) {
1620 $this->success( $metrics->getName() . ' current value is ' . (float) $samples[0]->getValue(), (float) $samples[0]->getValue(), $stdout );
1621 exit( 0 );
1622 }
1623 break;
1624 case 'histogram':
1625 $this->error( 12, $stdout );
1626 break;
1627 }
1628 }
1629 }
1630 $this->error( 9, $stdout );
1631 break;
1632 }
1633 }
1634
1635 /**
1636 * Get information about self-registered components.
1637 *
1638 * ## OPTIONS
1639 *
1640 * <list>
1641 * : The action to take.
1642 * ---
1643 * options:
1644 * - list
1645 * ---
1646 *
1647 * [--format=<format>]
1648 * : Allows overriding the output of the command when listing.
1649 * ---
1650 * default: table
1651 * options:
1652 * - table
1653 * - json
1654 * - csv
1655 * - yaml
1656 * - ids
1657 * - count
1658 * ---
1659 *
1660 * [--stdout]
1661 * : Use clean STDOUT output to use results in scripts. Unnecessary when piping commands because piping is detected by DecaLog.
1662 *
1663 * ## EXAMPLES
1664 *
1665 * Lists currently self-registered components:
1666 * + wp log selfreg list
1667 * + wp log selfreg list --format=json
1668 *
1669 *
1670 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1671 *
1672 */
1673 public function selfreg( $args, $assoc_args ) {
1674 $stdout = \WP_CLI\Utils\get_flag_value( $assoc_args, 'stdout', false );
1675 $format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'table' );
1676 $action = $args[0] ?? 'list';
1677 $list = SDK::get_selfreg();
1678 switch ( $action ) {
1679 case 'list':
1680 $detail = [ 'slug', 'name', 'version' ];
1681 if ( 'ids' === $format ) {
1682 $this->write_ids( $list );
1683 } elseif ( 'yaml' === $format ) {
1684 $details = Spyc::YAMLDump( $list, true, true, true );
1685 $this->line( $details, $details, $stdout );
1686 } elseif ( 'json' === $format ) {
1687 $details = wp_json_encode( $list );
1688 $this->line( $details, $details, $stdout );
1689 } else {
1690 \WP_CLI\Utils\format_items( $format, $list, $detail );
1691 }
1692 break;
1693 }
1694 }
1695
1696 /**
1697 * Display past or current events.
1698 *
1699 * ## OPTIONS
1700 *
1701 * [<count>]
1702 * : An integer value [1-60] indicating how many most recent events to display. If 0 or nothing is supplied as value, a live session is launched, displaying events as soon as they occur.
1703 *
1704 * [--level=<level>]
1705 * : The minimal level to display.
1706 * ---
1707 * default: info
1708 * options:
1709 * - info
1710 * - notice
1711 * - warning
1712 * - error
1713 * - critical
1714 * - alert
1715 * - emergency
1716 * ---
1717 *
1718 *[--filter=<filter>]
1719 * : The misc. filters to apply. Show only records matching the specified pattern.
1720 * MUST be a json string containing pairs "field":"regexp".
1721 * ---
1722 * default: '{}'
1723 * available fields: 'channel', 'message', 'class', 'source', 'code', 'site_id', 'user_id', 'remote_ip', 'url', 'verb', 'server','referrer', 'file', 'line', 'classname', 'function'
1724 * example: '{"source":"/Jetpack/", "remote_ip":"/(135.|164.)/"}'
1725 * ---
1726 *
1727 * [--format=<format>]
1728 * : Specifies the outputted event format.
1729 * ---
1730 * default: wp
1731 * options:
1732 * - wp
1733 * - http
1734 * - php
1735 * ---
1736 *
1737 * [--col=<columns>]
1738 * : The Number of columns (char in a row) to display. Default is 160. Min is 80 and max is 400.
1739 *
1740 * [--theme=<theme>]
1741 * : Modifies the colors scheme.
1742 * ---
1743 * default: standard
1744 * options:
1745 * - standard
1746 * - soft
1747 * ---
1748 *
1749 * [--yes]
1750 * : Answer yes to the confirmation message, if any.
1751 *
1752 * ## NOTES
1753 *
1754 * + This command needs shared memory support for PHP: the PHP module "shmop" must be activated in your PHP web configuration AND in your PHP command-line configuration.
1755 * + This command relies on an internal logger. If this logger is not started at launch time, you will be prompted to starting it - this logger may be left in the "running" state without impact on your website.
1756 * + This internal logger records events from info to emergency levels. It doesn't record debug-level events.
1757 * + If the logger has just been started there will not be much to display if <count> is different from 0...
1758 * + In a live session, just use CTRL-C to terminate it.
1759 *
1760 * ## EXAMPLES
1761 *
1762 * wp log tail
1763 * wp log tail 20
1764 * wp log tail 20 --level=warning
1765 * wp log tail --filter='{"source":"/Jetpack/", "remote_ip":"/(135.|164.)/"}'
1766 * wp log tail --filter='{"source":"/WordPress/"} --theme=soft --format=wp'
1767 *
1768 *
1769 * === For other examples and recipes, visit https://github.com/Pierre-Lannoy/wp-decalog/blob/master/WP-CLI.md ===
1770 *
1771 */
1772 public function tail( $args, $assoc_args ) {
1773 if ( ! function_exists( 'shmop_open' ) || ! function_exists( 'shmop_read' ) || ! function_exists( 'shmop_write' ) || ! function_exists( 'shmop_delete' ) ) {
1774 $this->error( 11 );
1775 }
1776 if ( ! Autolog::is_enabled() ) {
1777 \WP_CLI::warning( 'auto-logging is currently disabled. The tail command needs auto-logging...' );
1778 \WP_CLI::confirm( 'Would you like to enable auto-logging and to resume command?', $assoc_args );
1779 Autolog::activate();
1780 }
1781 $filters = [];
1782 $count = isset( $args[0] ) ? (int) $args[0] : 0;
1783 if ( 0 > $count || 60 < $count ) {
1784 $count = 0;
1785 }
1786 $col = isset( $assoc_args['col'] ) ? (int) $assoc_args['col'] : 160;
1787 if ( 80 > $col ) {
1788 $col = 80;
1789 }
1790 if ( 400 < $col ) {
1791 $col = 400;
1792 }
1793 $filter = \json_decode( isset( $assoc_args['filter'] ) ? (string) $assoc_args['filter'] : '{}', true );
1794 if ( is_array( $filter ) ) {
1795 foreach ( [ 'channel', 'message', 'class', 'source', 'code', 'site_id', 'user_id', 'remote_ip', 'url', 'verb', 'server', 'referrer', 'file', 'line', 'classname', 'function' ] as $field ) {
1796 if ( array_key_exists( $field, $filter ) ) {
1797 $value = (string) $filter[ $field ];
1798 if ( '' === $value ) {
1799 continue;
1800 }
1801 switch ( $field ) {
1802 case 'source':
1803 $filters['component'] = $value;
1804 break;
1805 default:
1806 $filters[ $field ] = $value;
1807 }
1808 }
1809 }
1810 }
1811 $level = isset( $assoc_args['level'] ) ? (string) $assoc_args['level'] : 'info';
1812 if ( ! in_array( $level, [ 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency' ], true ) ) {
1813 $this->error( 10 );
1814 }
1815 $filters['level'] = $level;
1816 $mode = isset( $assoc_args['format'] ) ? (string) $assoc_args['format'] : 'classic';
1817 $records = SharedMemoryHandler::read();
1818 if ( 0 === $count ) {
1819 $logger = Log::bootstrap( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
1820 $logger->notice( 'Live console launched.' );
1821 while ( true ) {
1822 $this->records_display( self::records_filter( SharedMemoryHandler::read(), $filters ), $mode, $assoc_args['theme'] ?? 'standard', $col );
1823 $this->flush();
1824 }
1825 } else {
1826 $this->records_display( array_slice( self::records_filter( $records, $filters ), -$count ), $mode, $assoc_args['theme'] ?? 'standard', $col );
1827 }
1828 }
1829
1830 /**
1831 * Get the WP-CLI help file.
1832 *
1833 * @param array $attributes 'style' => 'markdown', 'html'.
1834 * 'mode' => 'raw', 'clean'.
1835 * @return string The output of the shortcode, ready to print.
1836 * @since 1.0.0
1837 */
1838 public static function sc_get_helpfile( $attributes ) {
1839 $md = new Markdown();
1840 return $md->get_shortcode( 'WP-CLI.md', $attributes );
1841 }
1842
1843 }
1844
1845 add_shortcode( 'decalog-wpcli', [ 'Decalog\Plugin\Feature\Wpcli', 'sc_get_helpfile' ] );
1846
1847 if ( defined( 'WP_CLI' ) && WP_CLI ) {
1848 \WP_CLI::add_command( 'log', 'Decalog\Plugin\Feature\Wpcli' );
1849 }
1850
1851 //TODO: verify processors types
1852