PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Logger.php

http://github.com/katzgrau/KLogger
PHP | 349 lines | 170 code | 35 blank | 144 comment | 22 complexity | df4cfffd711b46c669f44f09996e7a9d MD5 | raw file
  1. <?php
  2. namespace Katzgrau\KLogger;
  3. use DateTime;
  4. use RuntimeException;
  5. use Psr\Log\AbstractLogger;
  6. use Psr\Log\LogLevel;
  7. /**
  8. * Finally, a light, permissions-checking logging class.
  9. *
  10. * Originally written for use with wpSearch
  11. *
  12. * Usage:
  13. * $log = new Katzgrau\KLogger\Logger('/var/log/', Psr\Log\LogLevel::INFO);
  14. * $log->info('Returned a million search results'); //Prints to the log file
  15. * $log->error('Oh dear.'); //Prints to the log file
  16. * $log->debug('x = 5'); //Prints nothing due to current severity threshhold
  17. *
  18. * @author Kenny Katzgrau <katzgrau@gmail.com>
  19. * @since July 26, 2008
  20. * @link https://github.com/katzgrau/KLogger
  21. * @version 1.0.0
  22. */
  23. /**
  24. * Class documentation
  25. */
  26. class Logger extends AbstractLogger
  27. {
  28. /**
  29. * KLogger options
  30. * Anything options not considered 'core' to the logging library should be
  31. * settable view the third parameter in the constructor
  32. *
  33. * Core options include the log file path and the log threshold
  34. *
  35. * @var array
  36. */
  37. protected $options = array (
  38. 'extension' => 'txt',
  39. 'dateFormat' => 'Y-m-d G:i:s.u',
  40. 'filename' => false,
  41. 'flushFrequency' => false,
  42. 'prefix' => 'log_',
  43. 'logFormat' => false,
  44. 'appendContext' => true,
  45. );
  46. /**
  47. * Path to the log file
  48. * @var string
  49. */
  50. private $logFilePath;
  51. /**
  52. * Current minimum logging threshold
  53. * @var integer
  54. */
  55. protected $logLevelThreshold = LogLevel::DEBUG;
  56. /**
  57. * The number of lines logged in this instance's lifetime
  58. * @var int
  59. */
  60. private $logLineCount = 0;
  61. /**
  62. * Log Levels
  63. * @var array
  64. */
  65. protected $logLevels = array(
  66. LogLevel::EMERGENCY => 0,
  67. LogLevel::ALERT => 1,
  68. LogLevel::CRITICAL => 2,
  69. LogLevel::ERROR => 3,
  70. LogLevel::WARNING => 4,
  71. LogLevel::NOTICE => 5,
  72. LogLevel::INFO => 6,
  73. LogLevel::DEBUG => 7
  74. );
  75. /**
  76. * This holds the file handle for this instance's log file
  77. * @var resource
  78. */
  79. private $fileHandle;
  80. /**
  81. * This holds the last line logged to the logger
  82. * Used for unit tests
  83. * @var string
  84. */
  85. private $lastLine = '';
  86. /**
  87. * Octal notation for default permissions of the log file
  88. * @var integer
  89. */
  90. private $defaultPermissions = 0777;
  91. /**
  92. * Class constructor
  93. *
  94. * @param string $logDirectory File path to the logging directory
  95. * @param string $logLevelThreshold The LogLevel Threshold
  96. * @param array $options
  97. *
  98. * @internal param string $logFilePrefix The prefix for the log file name
  99. * @internal param string $logFileExt The extension for the log file
  100. */
  101. public function __construct($logDirectory, $logLevelThreshold = LogLevel::DEBUG, array $options = array())
  102. {
  103. $this->logLevelThreshold = $logLevelThreshold;
  104. $this->options = array_merge($this->options, $options);
  105. $logDirectory = rtrim($logDirectory, DIRECTORY_SEPARATOR);
  106. if ( ! file_exists($logDirectory)) {
  107. mkdir($logDirectory, $this->defaultPermissions, true);
  108. }
  109. if(strpos($logDirectory, 'php://') === 0) {
  110. $this->setLogToStdOut($logDirectory);
  111. $this->setFileHandle('w+');
  112. } else {
  113. $this->setLogFilePath($logDirectory);
  114. if(file_exists($this->logFilePath) && !is_writable($this->logFilePath)) {
  115. throw new RuntimeException('The file could not be written to. Check that appropriate permissions have been set.');
  116. }
  117. $this->setFileHandle('a');
  118. }
  119. if ( ! $this->fileHandle) {
  120. throw new RuntimeException('The file could not be opened. Check permissions.');
  121. }
  122. }
  123. /**
  124. * @param string $stdOutPath
  125. */
  126. public function setLogToStdOut($stdOutPath) {
  127. $this->logFilePath = $stdOutPath;
  128. }
  129. /**
  130. * @param string $logDirectory
  131. */
  132. public function setLogFilePath($logDirectory) {
  133. if ($this->options['filename']) {
  134. if (strpos($this->options['filename'], '.log') !== false || strpos($this->options['filename'], '.txt') !== false) {
  135. $this->logFilePath = $logDirectory.DIRECTORY_SEPARATOR.$this->options['filename'];
  136. }
  137. else {
  138. $this->logFilePath = $logDirectory.DIRECTORY_SEPARATOR.$this->options['filename'].'.'.$this->options['extension'];
  139. }
  140. } else {
  141. $this->logFilePath = $logDirectory.DIRECTORY_SEPARATOR.$this->options['prefix'].date('Y-m-d').'.'.$this->options['extension'];
  142. }
  143. }
  144. /**
  145. * @param $writeMode
  146. *
  147. * @internal param resource $fileHandle
  148. */
  149. public function setFileHandle($writeMode) {
  150. $this->fileHandle = fopen($this->logFilePath, $writeMode);
  151. }
  152. /**
  153. * Class destructor
  154. */
  155. public function __destruct()
  156. {
  157. if ($this->fileHandle) {
  158. fclose($this->fileHandle);
  159. }
  160. }
  161. /**
  162. * Sets the date format used by all instances of KLogger
  163. *
  164. * @param string $dateFormat Valid format string for date()
  165. */
  166. public function setDateFormat($dateFormat)
  167. {
  168. $this->options['dateFormat'] = $dateFormat;
  169. }
  170. /**
  171. * Sets the Log Level Threshold
  172. *
  173. * @param string $logLevelThreshold The log level threshold
  174. */
  175. public function setLogLevelThreshold($logLevelThreshold)
  176. {
  177. $this->logLevelThreshold = $logLevelThreshold;
  178. }
  179. /**
  180. * Logs with an arbitrary level.
  181. *
  182. * @param mixed $level
  183. * @param string $message
  184. * @param array $context
  185. * @return null
  186. */
  187. public function log($level, $message, array $context = array())
  188. {
  189. if ($this->logLevels[$this->logLevelThreshold] < $this->logLevels[$level]) {
  190. return;
  191. }
  192. $message = $this->formatMessage($level, $message, $context);
  193. $this->write($message);
  194. }
  195. /**
  196. * Writes a line to the log without prepending a status or timestamp
  197. *
  198. * @param string $message Line to write to the log
  199. * @return void
  200. */
  201. public function write($message)
  202. {
  203. if (null !== $this->fileHandle) {
  204. if (fwrite($this->fileHandle, $message) === false) {
  205. throw new RuntimeException('The file could not be written to. Check that appropriate permissions have been set.');
  206. } else {
  207. $this->lastLine = trim($message);
  208. $this->logLineCount++;
  209. if ($this->options['flushFrequency'] && $this->logLineCount % $this->options['flushFrequency'] === 0) {
  210. fflush($this->fileHandle);
  211. }
  212. }
  213. }
  214. }
  215. /**
  216. * Get the file path that the log is currently writing to
  217. *
  218. * @return string
  219. */
  220. public function getLogFilePath()
  221. {
  222. return $this->logFilePath;
  223. }
  224. /**
  225. * Get the last line logged to the log file
  226. *
  227. * @return string
  228. */
  229. public function getLastLogLine()
  230. {
  231. return $this->lastLine;
  232. }
  233. /**
  234. * Formats the message for logging.
  235. *
  236. * @param string $level The Log Level of the message
  237. * @param string $message The message to log
  238. * @param array $context The context
  239. * @return string
  240. */
  241. protected function formatMessage($level, $message, $context)
  242. {
  243. if ($this->options['logFormat']) {
  244. $parts = array(
  245. 'date' => $this->getTimestamp(),
  246. 'level' => strtoupper($level),
  247. 'level-padding' => str_repeat(' ', 9 - strlen($level)),
  248. 'priority' => $this->logLevels[$level],
  249. 'message' => $message,
  250. 'context' => json_encode($context),
  251. );
  252. $message = $this->options['logFormat'];
  253. foreach ($parts as $part => $value) {
  254. $message = str_replace('{'.$part.'}', $value, $message);
  255. }
  256. } else {
  257. $message = "[{$this->getTimestamp()}] [{$level}] {$message}";
  258. }
  259. if ($this->options['appendContext'] && ! empty($context)) {
  260. $message .= PHP_EOL.$this->indent($this->contextToString($context));
  261. }
  262. return $message.PHP_EOL;
  263. }
  264. /**
  265. * Gets the correctly formatted Date/Time for the log entry.
  266. *
  267. * PHP DateTime is dump, and you have to resort to trickery to get microseconds
  268. * to work correctly, so here it is.
  269. *
  270. * @return string
  271. */
  272. private function getTimestamp()
  273. {
  274. $originalTime = microtime(true);
  275. $micro = sprintf("%06d", ($originalTime - floor($originalTime)) * 1000000);
  276. $date = new DateTime(date('Y-m-d H:i:s.'.$micro, $originalTime));
  277. return $date->format($this->options['dateFormat']);
  278. }
  279. /**
  280. * Takes the given context and coverts it to a string.
  281. *
  282. * @param array $context The Context
  283. * @return string
  284. */
  285. protected function contextToString($context)
  286. {
  287. $export = '';
  288. foreach ($context as $key => $value) {
  289. $export .= "{$key}: ";
  290. $export .= preg_replace(array(
  291. '/=>\s+([a-zA-Z])/im',
  292. '/array\(\s+\)/im',
  293. '/^ |\G /m'
  294. ), array(
  295. '=> $1',
  296. 'array()',
  297. ' '
  298. ), str_replace('array (', 'array(', var_export($value, true)));
  299. $export .= PHP_EOL;
  300. }
  301. return str_replace(array('\\\\', '\\\''), array('\\', '\''), rtrim($export));
  302. }
  303. /**
  304. * Indents the given string with the given indent.
  305. *
  306. * @param string $string The string to indent
  307. * @param string $indent What to use as the indent.
  308. * @return string
  309. */
  310. protected function indent($string, $indent = ' ')
  311. {
  312. return $indent.str_replace("\n", "\n".$indent, $string);
  313. }
  314. }