PluginProbe
DecaLog / 3.0.2
DecaLog v3.0.2
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 3.0.2, at includes/features/class-wpcli.php

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