1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Console; |
4: | |
5: | use Psr\Log\LoggerInterface; |
6: | use Salient\Console\Target\StreamTarget; |
7: | use Salient\Contract\Console\Target\HasPrefix; |
8: | use Salient\Contract\Console\Target\StreamTargetInterface; |
9: | use Salient\Contract\Console\Target\TargetInterface; |
10: | use Salient\Contract\Console\ConsoleInterface; |
11: | use Salient\Contract\Core\Exception\Exception; |
12: | use Salient\Contract\Core\Exception\MultipleErrorException; |
13: | use Salient\Contract\Core\Facade\FacadeAwareInterface; |
14: | use Salient\Contract\Core\Unloadable; |
15: | use Salient\Core\Concern\FacadeAwareInstanceTrait; |
16: | use Salient\Utility\Arr; |
17: | use Salient\Utility\Debug; |
18: | use Salient\Utility\Env; |
19: | use Salient\Utility\File; |
20: | use Salient\Utility\Format; |
21: | use Salient\Utility\Get; |
22: | use Salient\Utility\Inflect; |
23: | use Salient\Utility\Sys; |
24: | use Throwable; |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | class Console implements ConsoleInterface, FacadeAwareInterface, Unloadable |
32: | { |
33: | |
34: | use FacadeAwareInstanceTrait; |
35: | |
36: | private ConsoleState $State; |
37: | |
38: | |
39: | |
40: | |
41: | public function __construct() |
42: | { |
43: | $this->State = new ConsoleState(); |
44: | } |
45: | |
46: | |
47: | |
48: | |
49: | public function unload(): void |
50: | { |
51: | foreach ($this->State->Targets as $target) { |
52: | $this->deregisterTarget($target); |
53: | } |
54: | } |
55: | |
56: | |
57: | |
58: | |
59: | public function logger(): LoggerInterface |
60: | { |
61: | return $this->State->Logger ??= |
62: | new ConsoleLogger($this->getReturnable()); |
63: | } |
64: | |
65: | |
66: | |
67: | |
68: | public function registerTarget( |
69: | TargetInterface $target, |
70: | array $levels = Console::LEVELS_ALL |
71: | ) { |
72: | $id = spl_object_id($target); |
73: | $register = function (array &$byLevel) use ($target, $levels, $id) { |
74: | foreach ($levels as $level) { |
75: | $byLevel[$level][$id] = $target; |
76: | } |
77: | }; |
78: | $flags = 0; |
79: | if ($target instanceof StreamTargetInterface) { |
80: | if ($target->isStdout()) { |
81: | $flags |= self::TARGET_STDIO | self::TARGET_STDOUT; |
82: | $this->State->StdoutTarget = $target; |
83: | } |
84: | if ($target->isStderr()) { |
85: | $flags |= self::TARGET_STDIO | self::TARGET_STDERR; |
86: | $this->State->StderrTarget = $target; |
87: | } |
88: | if ($flags) { |
89: | $register($this->State->StdioTargetsByLevel); |
90: | } |
91: | if ($target->isTty()) { |
92: | $flags |= self::TARGET_TTY; |
93: | $register($this->State->TtyTargetsByLevel); |
94: | } |
95: | $flags |= self::TARGET_STREAM; |
96: | } |
97: | $register($this->State->TargetsByLevel); |
98: | $this->State->Targets[$id] = $target; |
99: | $this->State->TargetFlags[$id] = $flags; |
100: | return $this; |
101: | } |
102: | |
103: | |
104: | |
105: | |
106: | public function deregisterTarget(TargetInterface $target) |
107: | { |
108: | $id = spl_object_id($target); |
109: | $deregister = function (array &$byLevel) use ($id) { |
110: | foreach (array_keys($byLevel) as $level) { |
111: | unset($byLevel[$level][$id]); |
112: | if (!$byLevel[$level]) { |
113: | unset($byLevel[$level]); |
114: | } |
115: | } |
116: | }; |
117: | unset($this->State->Targets[$id]); |
118: | unset($this->State->TargetFlags[$id]); |
119: | $deregister($this->State->TargetsByLevel); |
120: | $deregister($this->State->TtyTargetsByLevel); |
121: | $deregister($this->State->StdioTargetsByLevel); |
122: | if ($target instanceof StreamTargetInterface) { |
123: | if ($target === $this->State->StderrTarget) { |
124: | $this->State->StderrTarget = Arr::last($this->filterTargets(self::TARGET_STDERR)); |
125: | } |
126: | if ($target === $this->State->StdoutTarget) { |
127: | $this->State->StdoutTarget = Arr::last($this->filterTargets(self::TARGET_STDOUT)); |
128: | } |
129: | } |
130: | return $this; |
131: | } |
132: | |
133: | |
134: | |
135: | |
136: | public function registerStderrTarget(?bool $debug = null) |
137: | { |
138: | if (\PHP_SAPI !== 'cli') { |
139: | return $this; |
140: | } |
141: | $debug ??= Env::getDebug(); |
142: | $stderr = $this->getStderrTarget(); |
143: | return $this |
144: | ->deregisterStdioTargets() |
145: | ->registerTarget($stderr, $debug ? self::LEVELS_ALL : self::LEVELS_ALL_EXCEPT_DEBUG); |
146: | } |
147: | |
148: | |
149: | |
150: | |
151: | public function registerStdioTargets(?bool $debug = null) |
152: | { |
153: | if (\PHP_SAPI !== 'cli') { |
154: | return $this; |
155: | } |
156: | $debug ??= Env::getDebug(); |
157: | $stderr = $this->getStderrTarget(); |
158: | $stdout = $this->getStdoutTarget(); |
159: | return $this |
160: | ->deregisterStdioTargets() |
161: | ->registerTarget($stderr, self::LEVELS_ERRORS_AND_WARNINGS) |
162: | ->registerTarget($stdout, $debug ? self::LEVELS_INFO : self::LEVELS_INFO_EXCEPT_DEBUG); |
163: | } |
164: | |
165: | |
166: | |
167: | |
168: | public function setPrefix(?string $prefix, int $targetFlags = 0) |
169: | { |
170: | foreach ($this->filterTargets($targetFlags) as $target) { |
171: | if ($target instanceof HasPrefix) { |
172: | $target->setPrefix($prefix); |
173: | } |
174: | } |
175: | return $this; |
176: | } |
177: | |
178: | |
179: | |
180: | |
181: | public function getTargets(?int $level = null, int $targetFlags = 0): array |
182: | { |
183: | $targets = $level === null |
184: | ? $this->State->Targets |
185: | : $this->State->TargetsByLevel[$level] ?? []; |
186: | return array_values($targetFlags |
187: | ? $this->filterTargets($targetFlags, $targets) |
188: | : $targets); |
189: | } |
190: | |
191: | |
192: | |
193: | |
194: | public function getStdoutTarget(): StreamTargetInterface |
195: | { |
196: | return $this->State->StdoutTarget ??= |
197: | new StreamTarget(\STDOUT); |
198: | } |
199: | |
200: | |
201: | |
202: | |
203: | public function getStderrTarget(): StreamTargetInterface |
204: | { |
205: | return $this->State->StderrTarget ??= |
206: | new StreamTarget(\STDERR); |
207: | } |
208: | |
209: | |
210: | |
211: | |
212: | public function getTtyTarget(): StreamTargetInterface |
213: | { |
214: | return ($stderr = $this->getStderrTarget())->isTty() |
215: | ? $stderr |
216: | : (($stdout = $this->getStdoutTarget())->isTty() |
217: | ? $stdout |
218: | : $stderr); |
219: | } |
220: | |
221: | |
222: | |
223: | |
224: | public function escape(string $string, bool $escapeNewlines = false): string |
225: | { |
226: | return ConsoleUtil::escape($string, $escapeNewlines); |
227: | } |
228: | |
229: | |
230: | |
231: | |
232: | public function removeEscapes(string $string): string |
233: | { |
234: | return ConsoleUtil::removeEscapes($string); |
235: | } |
236: | |
237: | |
238: | |
239: | |
240: | public function removeTags(string $string): string |
241: | { |
242: | return ConsoleUtil::removeTags($string); |
243: | } |
244: | |
245: | |
246: | |
247: | |
248: | public function error(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, bool $count = true) |
249: | { |
250: | return $this->write(self::LEVEL_ERROR, $msg1, $msg2, false, $ex, $count); |
251: | } |
252: | |
253: | |
254: | |
255: | |
256: | public function errorOnce(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, bool $count = true) |
257: | { |
258: | return $this->write(self::LEVEL_ERROR, $msg1, $msg2, true, $ex, $count); |
259: | } |
260: | |
261: | |
262: | |
263: | |
264: | public function warn(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, bool $count = true) |
265: | { |
266: | return $this->write(self::LEVEL_WARNING, $msg1, $msg2, false, $ex, $count); |
267: | } |
268: | |
269: | |
270: | |
271: | |
272: | public function warnOnce(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, bool $count = true) |
273: | { |
274: | return $this->write(self::LEVEL_WARNING, $msg1, $msg2, true, $ex, $count); |
275: | } |
276: | |
277: | |
278: | |
279: | |
280: | public function group(string $msg1, ?string $msg2 = null, ?string $endMsg1 = null, ?string $endMsg2 = null) |
281: | { |
282: | $this->State->Groups++; |
283: | $this->State->GroupMessages[] = [$endMsg1 ?? ($endMsg2 === null ? null : ''), $endMsg2]; |
284: | return $this->write(self::LEVEL_NOTICE, $msg1, $msg2, false, null, true, self::TYPE_GROUP_START); |
285: | } |
286: | |
287: | |
288: | |
289: | |
290: | public function groupEnd() |
291: | { |
292: | [$msg1, $msg2] = array_pop($this->State->GroupMessages) ?? [null, null]; |
293: | if ($msg1 !== null) { |
294: | $this->write(self::LEVEL_NOTICE, $msg1, $msg2, false, null, true, self::TYPE_GROUP_END); |
295: | } |
296: | if ( |
297: | !$this->State->LastWriteWasEmptyGroupEnd |
298: | && ($targets = $this->getTargets(self::LEVEL_NOTICE, self::TARGET_STDIO | self::TARGET_TTY)) |
299: | ) { |
300: | $targets = [self::LEVEL_NOTICE => $targets]; |
301: | $this->write(self::LEVEL_NOTICE, '', null, false, null, false, self::TYPE_UNFORMATTED, $targets); |
302: | $this->State->LastWriteWasEmptyGroupEnd = true; |
303: | } |
304: | if ($this->State->Groups > -1) { |
305: | $this->State->Groups--; |
306: | } |
307: | return $this; |
308: | } |
309: | |
310: | |
311: | |
312: | |
313: | public function info(string $msg1, ?string $msg2 = null) |
314: | { |
315: | return $this->write(self::LEVEL_NOTICE, $msg1, $msg2); |
316: | } |
317: | |
318: | |
319: | |
320: | |
321: | public function infoOnce(string $msg1, ?string $msg2 = null) |
322: | { |
323: | return $this->write(self::LEVEL_NOTICE, $msg1, $msg2, true); |
324: | } |
325: | |
326: | |
327: | |
328: | |
329: | public function log(string $msg1, ?string $msg2 = null) |
330: | { |
331: | return $this->write(self::LEVEL_INFO, $msg1, $msg2); |
332: | } |
333: | |
334: | |
335: | |
336: | |
337: | public function logOnce(string $msg1, ?string $msg2 = null) |
338: | { |
339: | return $this->write(self::LEVEL_INFO, $msg1, $msg2, true); |
340: | } |
341: | |
342: | |
343: | |
344: | |
345: | public function logProgress(string $msg1, ?string $msg2 = null) |
346: | { |
347: | if ($msg2 === null || $msg2 === '') { |
348: | $msg1 = rtrim($msg1, "\r") . "\r"; |
349: | } else { |
350: | $msg2 = rtrim($msg2, "\r") . "\r"; |
351: | } |
352: | return $this->write(self::LEVEL_INFO, $msg1, $msg2, false, null, false, self::TYPE_PROGRESS, $this->State->TtyTargetsByLevel); |
353: | } |
354: | |
355: | |
356: | |
357: | |
358: | public function clearProgress() |
359: | { |
360: | return $this->write(self::LEVEL_INFO, "\r", null, false, null, false, self::TYPE_UNFORMATTED, $this->State->TtyTargetsByLevel); |
361: | } |
362: | |
363: | |
364: | |
365: | |
366: | public function debug(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, int $depth = 0) |
367: | { |
368: | return $this->doDebug($msg1, $msg2, $ex, $depth); |
369: | } |
370: | |
371: | |
372: | |
373: | |
374: | public function debugOnce(string $msg1, ?string $msg2 = null, ?Throwable $ex = null, int $depth = 0) |
375: | { |
376: | return $this->doDebug($msg1, $msg2, $ex, $depth, true); |
377: | } |
378: | |
379: | |
380: | |
381: | |
382: | private function doDebug(string $msg1, ?string $msg2, ?Throwable $ex, int $depth, bool $once = false) |
383: | { |
384: | $this->Facade === null || $depth++; |
385: | if ($msg1 !== '') { |
386: | $msg1 = " __{$msg1}__"; |
387: | } |
388: | $msg1 = '{' . implode('', Debug::getCaller($depth + 1)) . '}' . $msg1; |
389: | return $this->write(self::LEVEL_DEBUG, $msg1, $msg2, $once, $ex); |
390: | } |
391: | |
392: | |
393: | |
394: | |
395: | public function message( |
396: | string $msg1, |
397: | ?string $msg2 = null, |
398: | int $level = Console::LEVEL_INFO, |
399: | int $type = Console::TYPE_UNDECORATED, |
400: | ?Throwable $ex = null, |
401: | bool $count = true |
402: | ) { |
403: | return $this->write($level, $msg1, $msg2, false, $ex, $count, $type); |
404: | } |
405: | |
406: | |
407: | |
408: | |
409: | public function messageOnce( |
410: | string $msg1, |
411: | ?string $msg2 = null, |
412: | int $level = Console::LEVEL_INFO, |
413: | int $type = Console::TYPE_UNDECORATED, |
414: | ?Throwable $ex = null, |
415: | bool $count = true |
416: | ) { |
417: | return $this->write($level, $msg1, $msg2, true, $ex, $count, $type); |
418: | } |
419: | |
420: | |
421: | |
422: | |
423: | public function exception( |
424: | Throwable $exception, |
425: | int $level = Console::LEVEL_ERROR, |
426: | ?int $traceLevel = Console::LEVEL_DEBUG, |
427: | bool $count = true |
428: | ) { |
429: | $addLine = $level <= self::LEVEL_ERROR || Env::getDebug(); |
430: | $msg1 = $this->escape(Get::basename(get_class($exception))) . ':'; |
431: | $ex = $exception; |
432: | $msg2 = ''; |
433: | do { |
434: | if ($ex !== $exception) { |
435: | $msg2 .= sprintf( |
436: | "\nCaused by __%s__: ", |
437: | $this->escape(Get::basename(get_class($ex))), |
438: | ); |
439: | } |
440: | $msg2 .= $this->escape( |
441: | $ex instanceof MultipleErrorException && !$ex->hasUnreportedErrors() |
442: | ? $ex->getMessageOnly() |
443: | : $ex->getMessage() |
444: | ); |
445: | if ($addLine) { |
446: | $msg2 .= sprintf( |
447: | ' ~~in %s:%d~~', |
448: | $this->escape($ex->getFile()), |
449: | $ex->getLine(), |
450: | ); |
451: | } |
452: | } while ($ex = $ex->getPrevious()); |
453: | |
454: | $this->State->Msg2HasTags = true; |
455: | try { |
456: | $this->write($level, $msg1, $msg2, false, $exception, $count); |
457: | } finally { |
458: | $this->State->Msg2HasTags = false; |
459: | } |
460: | |
461: | if ($traceLevel !== null) { |
462: | $this->write($traceLevel, 'Stack trace:', "\n" . $exception->getTraceAsString()); |
463: | if ($exception instanceof Exception) { |
464: | foreach ($exception->getMetadata() as $key => $value) { |
465: | $this->write($traceLevel, $key . ':', "\n" . rtrim((string) $value, "\n")); |
466: | } |
467: | } |
468: | } |
469: | |
470: | return $this; |
471: | } |
472: | |
473: | |
474: | |
475: | |
476: | public function summary( |
477: | string $finishedText = 'Command finished', |
478: | string $successText = 'without errors', |
479: | bool $withResourceUsage = false, |
480: | bool $withoutErrorsAndWarnings = false, |
481: | bool $withGenericType = false |
482: | ) { |
483: | $errors = $this->State->Errors; |
484: | $warnings = $this->State->Warnings; |
485: | $hasErrors = $errors || $warnings; |
486: | $msg[] = rtrim($finishedText); |
487: | if (!$hasErrors) { |
488: | $msg[] = $successText; |
489: | } elseif (!$withoutErrorsAndWarnings) { |
490: | $msg[] = 'with ' . Inflect::format($errors, '{{#}} {{#:error}}') |
491: | . ($warnings |
492: | ? ' and ' . Inflect::format($warnings, '{{#}} {{#:warning}}') |
493: | : ''); |
494: | } |
495: | if ($withResourceUsage) { |
496: | |
497: | $requestTime = $_SERVER['REQUEST_TIME_FLOAT']; |
498: | $msg[] = sprintf( |
499: | 'in %.3fs (%s memory used)', |
500: | microtime(true) - $requestTime, |
501: | Format::bytes(memory_get_peak_usage()), |
502: | ); |
503: | } |
504: | |
505: | return $this->write( |
506: | !$hasErrors || $withoutErrorsAndWarnings || $withGenericType |
507: | ? self::LEVEL_INFO |
508: | : ($errors ? self::LEVEL_ERROR : self::LEVEL_WARNING), |
509: | Arr::implode(' ', $msg, ''), |
510: | null, |
511: | false, |
512: | null, |
513: | false, |
514: | ($hasErrors && $withoutErrorsAndWarnings) || $withGenericType |
515: | ? self::TYPE_SUMMARY |
516: | : ($hasErrors ? self::TYPE_FAILURE : self::TYPE_SUCCESS), |
517: | ); |
518: | } |
519: | |
520: | |
521: | |
522: | |
523: | public function print(string $msg, int $level = Console::LEVEL_INFO) |
524: | { |
525: | return $this->write($level, $msg, null, false, null, false, self::TYPE_UNFORMATTED); |
526: | } |
527: | |
528: | |
529: | |
530: | |
531: | public function printStdio(string $msg, int $level = Console::LEVEL_INFO) |
532: | { |
533: | return $this->write($level, $msg, null, false, null, false, self::TYPE_UNFORMATTED, $this->State->StdioTargetsByLevel); |
534: | } |
535: | |
536: | |
537: | |
538: | |
539: | public function printTty(string $msg, int $level = Console::LEVEL_INFO) |
540: | { |
541: | return $this->write($level, $msg, null, false, null, false, self::TYPE_UNFORMATTED, $this->State->TtyTargetsByLevel); |
542: | } |
543: | |
544: | |
545: | |
546: | |
547: | public function printStdout(string $msg, int $level = Console::LEVEL_INFO) |
548: | { |
549: | $targets = [$level => [$this->getStdoutTarget()]]; |
550: | return $this->write($level, $msg, null, false, null, false, self::TYPE_UNFORMATTED, $targets); |
551: | } |
552: | |
553: | |
554: | |
555: | |
556: | public function count(int $level) |
557: | { |
558: | if ($level <= self::LEVEL_ERROR) { |
559: | $this->State->Errors++; |
560: | } elseif ($level === self::LEVEL_WARNING) { |
561: | $this->State->Warnings++; |
562: | } |
563: | return $this; |
564: | } |
565: | |
566: | |
567: | |
568: | |
569: | public function errors(): int |
570: | { |
571: | return $this->State->Errors; |
572: | } |
573: | |
574: | |
575: | |
576: | |
577: | public function warnings(): int |
578: | { |
579: | return $this->State->Warnings; |
580: | } |
581: | |
582: | |
583: | |
584: | |
585: | protected function deregisterStdioTargets() |
586: | { |
587: | foreach ($this->filterTargets(self::TARGET_STDIO) as $target) { |
588: | $this->deregisterTarget($target); |
589: | } |
590: | return $this; |
591: | } |
592: | |
593: | |
594: | |
595: | |
596: | |
597: | |
598: | protected function filterTargets(int $flags, ?array $targets = null): array |
599: | { |
600: | $targets ??= $this->State->Targets; |
601: | $invert = false; |
602: | if ($flags & self::TARGET_INVERT) { |
603: | $flags &= ~self::TARGET_INVERT; |
604: | $invert = true; |
605: | } |
606: | if (!$flags) { |
607: | return $targets; |
608: | } |
609: | foreach ($targets as $id => $target) { |
610: | if ( |
611: | $this->State->TargetFlags[$id] & $flags |
612: | xor $invert |
613: | ) { |
614: | $filtered[$id] = $target; |
615: | } |
616: | } |
617: | return $filtered ?? []; |
618: | } |
619: | |
620: | |
621: | |
622: | |
623: | |
624: | |
625: | |
626: | |
627: | |
628: | |
629: | protected function write( |
630: | int $level, |
631: | string $msg1, |
632: | ?string $msg2, |
633: | bool $once = false, |
634: | ?Throwable $ex = null, |
635: | bool $count = true, |
636: | int $type = self::TYPE_STANDARD, |
637: | ?array &$targets = null |
638: | ) { |
639: | if ($count && $level <= self::LEVEL_WARNING) { |
640: | $this->count($level); |
641: | } |
642: | |
643: | if ($once) { |
644: | $hash = Get::hash(implode("\0", [$level, $msg1, $msg2, $type])); |
645: | if (isset($this->State->Written[$hash])) { |
646: | return $this; |
647: | } |
648: | $this->State->Written[$hash] = true; |
649: | } |
650: | |
651: | if (!$this->State->Targets && !$targets) { |
652: | $this->registerStderrTarget(); |
653: | $this->registerTarget(StreamTarget::fromFile(sprintf( |
654: | '%s/%s-%s-%s.log', |
655: | Sys::getTempDir(), |
656: | Sys::getProgramBasename(), |
657: | Get::hash(File::realpath(Sys::getProgramName())), |
658: | Sys::getUserId(), |
659: | )), Env::getDebug() ? self::LEVELS_ALL : self::LEVELS_ALL_EXCEPT_DEBUG); |
660: | } |
661: | |
662: | $this->State->LastWriteWasEmptyGroupEnd = false; |
663: | |
664: | if ($msg2 === '') { |
665: | $msg2 = null; |
666: | $msg2HasNewline = false; |
667: | } else { |
668: | $msg2HasNewline = $msg2 !== null && strpos($msg2, "\n") !== false; |
669: | if ($msg2HasNewline) { |
670: | $msg2 = "\n" . ltrim($msg2); |
671: | } |
672: | } |
673: | |
674: | |
675: | |
676: | $context = $ex ? ['exception' => $ex] : []; |
677: | $groupIndent = max(0, $this->State->Groups * 2); |
678: | $msg1HasNewline = $msg1 !== '' && strpos($msg1, "\n") !== false; |
679: | $_targets = $targets ?? $this->State->TargetsByLevel; |
680: | foreach ($_targets[$level] ?? [] as $target) { |
681: | $formatter = $target->getFormatter(); |
682: | $prefixWidth = mb_strlen($formatter->getMessagePrefix($level, $type)); |
683: | $indent = $groupIndent + ( |
684: | $msg1HasNewline || $prefixWidth < 4 |
685: | ? $prefixWidth |
686: | : $prefixWidth - 4 |
687: | ); |
688: | $_msg1 = $msg1 === '' ? '' : $formatter->format($msg1); |
689: | if ($indent && $msg1HasNewline) { |
690: | $_msg1 = str_replace("\n", "\n" . str_repeat(' ', $indent), $_msg1); |
691: | } |
692: | if ($msg2 === null) { |
693: | $_msg2 = null; |
694: | } else { |
695: | $_msg2 = $this->State->Msg2HasTags ? $formatter->format($msg2) : $msg2; |
696: | if ($msg2HasNewline) { |
697: | $_msg2 = str_replace("\n", "\n" . str_repeat(' ', $indent + 2), $_msg2); |
698: | } elseif ($_msg1 !== '') { |
699: | $_msg2 = ' ' . $_msg2; |
700: | } |
701: | } |
702: | $message = $formatter->formatMessage($_msg1, $_msg2, $level, $type); |
703: | if ($groupIndent && $message !== '') { |
704: | $message = str_repeat(' ', $groupIndent) . $message; |
705: | } |
706: | $target->write($level, $message, $context); |
707: | } |
708: | |
709: | return $this; |
710: | } |
711: | |
712: | |
713: | |
714: | |
715: | |
716: | |
717: | protected function getReturnable() |
718: | { |
719: | return $this->Facade === null |
720: | ? $this |
721: | : $this->withoutFacade($this->Facade, false); |
722: | } |
723: | } |
724: | |
725: | |
726: | |
727: | |
728: | final class ConsoleState |
729: | { |
730: | |
731: | public array $Targets = []; |
732: | |
733: | public array $TargetFlags = []; |
734: | |
735: | public array $TargetsByLevel = []; |
736: | |
737: | public array $StdioTargetsByLevel = []; |
738: | |
739: | public array $TtyTargetsByLevel = []; |
740: | public ?StreamTargetInterface $StdoutTarget = null; |
741: | public ?StreamTargetInterface $StderrTarget = null; |
742: | |
743: | public array $Written = []; |
744: | public int $Groups = -1; |
745: | |
746: | public array $GroupMessages = []; |
747: | public bool $LastWriteWasEmptyGroupEnd = false; |
748: | public bool $Msg2HasTags = false; |
749: | public int $Errors = 0; |
750: | public int $Warnings = 0; |
751: | public LoggerInterface $Logger; |
752: | |
753: | private function __clone() {} |
754: | } |
755: | |