1<?php declare(strict_types=1); 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog\Formatter; 13 14use MongoDB\BSON\Type; 15use MongoDB\BSON\UTCDateTime; 16use Monolog\Utils; 17 18/** 19 * Formats a record for use with the MongoDBHandler. 20 * 21 * @author Florian Plattner <me@florianplattner.de> 22 */ 23class MongoDBFormatter implements FormatterInterface 24{ 25 /** @var bool */ 26 private $exceptionTraceAsString; 27 /** @var int */ 28 private $maxNestingLevel; 29 /** @var bool */ 30 private $isLegacyMongoExt; 31 32 /** 33 * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2 34 * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings 35 */ 36 public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) 37 { 38 $this->maxNestingLevel = max($maxNestingLevel, 0); 39 $this->exceptionTraceAsString = $exceptionTraceAsString; 40 41 $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); 42 } 43 44 /** 45 * {@inheritDoc} 46 * 47 * @return mixed[] 48 */ 49 public function format(array $record): array 50 { 51 /** @var mixed[] $res */ 52 $res = $this->formatArray($record); 53 54 return $res; 55 } 56 57 /** 58 * {@inheritDoc} 59 * 60 * @return array<mixed[]> 61 */ 62 public function formatBatch(array $records): array 63 { 64 $formatted = []; 65 foreach ($records as $key => $record) { 66 $formatted[$key] = $this->format($record); 67 } 68 69 return $formatted; 70 } 71 72 /** 73 * @param mixed[] $array 74 * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" 75 */ 76 protected function formatArray(array $array, int $nestingLevel = 0) 77 { 78 if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { 79 return '[...]'; 80 } 81 82 foreach ($array as $name => $value) { 83 if ($value instanceof \DateTimeInterface) { 84 $array[$name] = $this->formatDate($value, $nestingLevel + 1); 85 } elseif ($value instanceof \Throwable) { 86 $array[$name] = $this->formatException($value, $nestingLevel + 1); 87 } elseif (is_array($value)) { 88 $array[$name] = $this->formatArray($value, $nestingLevel + 1); 89 } elseif (is_object($value) && !$value instanceof Type) { 90 $array[$name] = $this->formatObject($value, $nestingLevel + 1); 91 } 92 } 93 94 return $array; 95 } 96 97 /** 98 * @param mixed $value 99 * @return mixed[]|string 100 */ 101 protected function formatObject($value, int $nestingLevel) 102 { 103 $objectVars = get_object_vars($value); 104 $objectVars['class'] = Utils::getClass($value); 105 106 return $this->formatArray($objectVars, $nestingLevel); 107 } 108 109 /** 110 * @return mixed[]|string 111 */ 112 protected function formatException(\Throwable $exception, int $nestingLevel) 113 { 114 $formattedException = [ 115 'class' => Utils::getClass($exception), 116 'message' => $exception->getMessage(), 117 'code' => (int) $exception->getCode(), 118 'file' => $exception->getFile() . ':' . $exception->getLine(), 119 ]; 120 121 if ($this->exceptionTraceAsString === true) { 122 $formattedException['trace'] = $exception->getTraceAsString(); 123 } else { 124 $formattedException['trace'] = $exception->getTrace(); 125 } 126 127 return $this->formatArray($formattedException, $nestingLevel); 128 } 129 130 protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime 131 { 132 if ($this->isLegacyMongoExt) { 133 return $this->legacyGetMongoDbDateTime($value); 134 } 135 136 return $this->getMongoDbDateTime($value); 137 } 138 139 private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime 140 { 141 return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); 142 } 143 144 /** 145 * This is needed to support MongoDB Driver v1.19 and below 146 * 147 * See https://github.com/mongodb/mongo-php-driver/issues/426 148 * 149 * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted 150 */ 151 private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime 152 { 153 $milliseconds = floor(((float) $value->format('U.u')) * 1000); 154 155 $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? 156 ? (int) $milliseconds 157 : (string) $milliseconds; 158 159 // @phpstan-ignore-next-line 160 return new UTCDateTime($milliseconds); 161 } 162} 163