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 Monolog\Logger; 15use Monolog\Utils; 16 17/** 18 * Formats incoming records into an HTML table 19 * 20 * This is especially useful for html email logging 21 * 22 * @author Tiago Brito <tlfbrito@gmail.com> 23 */ 24class HtmlFormatter extends NormalizerFormatter 25{ 26 /** 27 * Translates Monolog log levels to html color priorities. 28 * 29 * @var array<int, string> 30 */ 31 protected $logLevels = [ 32 Logger::DEBUG => '#CCCCCC', 33 Logger::INFO => '#28A745', 34 Logger::NOTICE => '#17A2B8', 35 Logger::WARNING => '#FFC107', 36 Logger::ERROR => '#FD7E14', 37 Logger::CRITICAL => '#DC3545', 38 Logger::ALERT => '#821722', 39 Logger::EMERGENCY => '#000000', 40 ]; 41 42 /** 43 * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format 44 */ 45 public function __construct(?string $dateFormat = null) 46 { 47 parent::__construct($dateFormat); 48 } 49 50 /** 51 * Creates an HTML table row 52 * 53 * @param string $th Row header content 54 * @param string $td Row standard cell content 55 * @param bool $escapeTd false if td content must not be html escaped 56 */ 57 protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string 58 { 59 $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); 60 if ($escapeTd) { 61 $td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>'; 62 } 63 64 return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>"; 65 } 66 67 /** 68 * Create a HTML h1 tag 69 * 70 * @param string $title Text to be in the h1 71 * @param int $level Error level 72 * @return string 73 */ 74 protected function addTitle(string $title, int $level): string 75 { 76 $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); 77 78 return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>'; 79 } 80 81 /** 82 * Formats a log record. 83 * 84 * @return string The formatted record 85 */ 86 public function format(array $record): string 87 { 88 $output = $this->addTitle($record['level_name'], $record['level']); 89 $output .= '<table cellspacing="1" width="100%" class="monolog-output">'; 90 91 $output .= $this->addRow('Message', (string) $record['message']); 92 $output .= $this->addRow('Time', $this->formatDate($record['datetime'])); 93 $output .= $this->addRow('Channel', $record['channel']); 94 if ($record['context']) { 95 $embeddedTable = '<table cellspacing="1" width="100%">'; 96 foreach ($record['context'] as $key => $value) { 97 $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); 98 } 99 $embeddedTable .= '</table>'; 100 $output .= $this->addRow('Context', $embeddedTable, false); 101 } 102 if ($record['extra']) { 103 $embeddedTable = '<table cellspacing="1" width="100%">'; 104 foreach ($record['extra'] as $key => $value) { 105 $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); 106 } 107 $embeddedTable .= '</table>'; 108 $output .= $this->addRow('Extra', $embeddedTable, false); 109 } 110 111 return $output.'</table>'; 112 } 113 114 /** 115 * Formats a set of log records. 116 * 117 * @return string The formatted set of records 118 */ 119 public function formatBatch(array $records): string 120 { 121 $message = ''; 122 foreach ($records as $record) { 123 $message .= $this->format($record); 124 } 125 126 return $message; 127 } 128 129 /** 130 * @param mixed $data 131 */ 132 protected function convertToString($data): string 133 { 134 if (null === $data || is_scalar($data)) { 135 return (string) $data; 136 } 137 138 $data = $this->normalize($data); 139 140 return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); 141 } 142} 143