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\Handler; 13 14use DateTimeInterface; 15use Monolog\Logger; 16use Monolog\Handler\SyslogUdp\UdpSocket; 17use Monolog\Utils; 18 19/** 20 * A Handler for logging to a remote syslogd server. 21 * 22 * @author Jesper Skovgaard Nielsen <nulpunkt@gmail.com> 23 * @author Dominik Kukacka <dominik.kukacka@gmail.com> 24 */ 25class SyslogUdpHandler extends AbstractSyslogHandler 26{ 27 const RFC3164 = 0; 28 const RFC5424 = 1; 29 const RFC5424e = 2; 30 31 /** @var array<self::RFC*, string> */ 32 private $dateFormats = array( 33 self::RFC3164 => 'M d H:i:s', 34 self::RFC5424 => \DateTime::RFC3339, 35 self::RFC5424e => \DateTime::RFC3339_EXTENDED, 36 ); 37 38 /** @var UdpSocket */ 39 protected $socket; 40 /** @var string */ 41 protected $ident; 42 /** @var self::RFC* */ 43 protected $rfc; 44 45 /** 46 * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) 47 * @param int $port Port number, or 0 if $host is a unix socket 48 * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant 49 * @param bool $bubble Whether the messages that are handled can bubble up the stack or not 50 * @param string $ident Program name or tag for each log message. 51 * @param int $rfc RFC to format the message for. 52 * @throws MissingExtensionException 53 * 54 * @phpstan-param self::RFC* $rfc 55 */ 56 public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) 57 { 58 if (!extension_loaded('sockets')) { 59 throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); 60 } 61 62 parent::__construct($facility, $level, $bubble); 63 64 $this->ident = $ident; 65 $this->rfc = $rfc; 66 67 $this->socket = new UdpSocket($host, $port); 68 } 69 70 protected function write(array $record): void 71 { 72 $lines = $this->splitMessageIntoLines($record['formatted']); 73 74 $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); 75 76 foreach ($lines as $line) { 77 $this->socket->write($line, $header); 78 } 79 } 80 81 public function close(): void 82 { 83 $this->socket->close(); 84 } 85 86 /** 87 * @param string|string[] $message 88 * @return string[] 89 */ 90 private function splitMessageIntoLines($message): array 91 { 92 if (is_array($message)) { 93 $message = implode("\n", $message); 94 } 95 96 $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); 97 if (false === $lines) { 98 $pcreErrorCode = preg_last_error(); 99 throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); 100 } 101 102 return $lines; 103 } 104 105 /** 106 * Make common syslog header (see rfc5424 or rfc3164) 107 */ 108 protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string 109 { 110 $priority = $severity + $this->facility; 111 112 if (!$pid = getmypid()) { 113 $pid = '-'; 114 } 115 116 if (!$hostname = gethostname()) { 117 $hostname = '-'; 118 } 119 120 if ($this->rfc === self::RFC3164) { 121 // see https://github.com/phpstan/phpstan/issues/5348 122 // @phpstan-ignore-next-line 123 $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); 124 $date = $dateNew->format($this->dateFormats[$this->rfc]); 125 126 return "<$priority>" . 127 $date . " " . 128 $hostname . " " . 129 $this->ident . "[" . $pid . "]: "; 130 } 131 132 $date = $datetime->format($this->dateFormats[$this->rfc]); 133 134 return "<$priority>1 " . 135 $date . " " . 136 $hostname . " " . 137 $this->ident . " " . 138 $pid . " - - "; 139 } 140 141 /** 142 * Inject your own socket, mainly used for testing 143 */ 144 public function setSocket(UdpSocket $socket): self 145 { 146 $this->socket = $socket; 147 148 return $this; 149 } 150} 151