diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -744,6 +744,7 @@ 'PhutilEnglishCanadaLocale' => 'internationalization/locales/PhutilEnglishCanadaLocale.php', 'PhutilErrorHandler' => 'error/PhutilErrorHandler.php', 'PhutilErrorHandlerTestCase' => 'error/__tests__/PhutilErrorHandlerTestCase.php', + 'PhutilErrorLog' => 'filesystem/PhutilErrorLog.php', 'PhutilErrorTrap' => 'error/PhutilErrorTrap.php', 'PhutilEvent' => 'events/PhutilEvent.php', 'PhutilEventConstants' => 'events/constant/PhutilEventConstants.php', @@ -1816,6 +1817,7 @@ 'PhutilEnglishCanadaLocale' => 'PhutilLocale', 'PhutilErrorHandler' => 'Phobject', 'PhutilErrorHandlerTestCase' => 'PhutilTestCase', + 'PhutilErrorLog' => 'Phobject', 'PhutilErrorTrap' => 'Phobject', 'PhutilEvent' => 'Phobject', 'PhutilEventConstants' => 'Phobject', diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -52,7 +52,7 @@ * Make assertions about the state of path in preparation for * writeFile() and writeFileIfChanged(). */ - private static function assertWritableFile($path) { + public static function assertWritableFile($path) { $path = self::resolvePath($path); $dir = dirname($path); diff --git a/src/filesystem/PhutilErrorLog.php b/src/filesystem/PhutilErrorLog.php new file mode 100644 --- /dev/null +++ b/src/filesystem/PhutilErrorLog.php @@ -0,0 +1,101 @@ +logName = $log_name; + return $this; + } + + public function getLogName() { + return $this->logName; + } + + public function setLogPath($log_path) { + $this->logPath = $log_path; + return $this; + } + + public function getLogPath() { + return $this->logPath; + } + + public function activateLog() { + $log_path = $this->getLogPath(); + + if ($log_path !== null) { + // Test that the path is writable. + $write_exception = null; + try { + Filesystem::assertWritableFile($log_path); + } catch (FilesystemException $ex) { + $write_exception = $ex; + } + + // If we hit an exception, try to create the containing directory. + if ($write_exception) { + $log_dir = dirname($log_path); + if (!Filesystem::pathExists($log_dir)) { + try { + Filesystem::createDirectory($log_dir, 0755, true); + } catch (FilesystemException $ex) { + throw new PhutilProxyException( + pht( + 'Unable to write log "%s" to path "%s". The containing '. + 'directory ("%s") does not exist or is not readable, and '. + 'could not be created.', + $this->getLogName(), + $log_path, + $log_dir), + $ex); + } + } + + // If we created the parent directory, test if the path is writable + // again. + try { + Filesystem::assertWritableFile($log_path); + $write_exception = null; + } catch (FilesystemException $ex) { + $write_exception = $ex; + } + } + + // If we ran into a write exception and couldn't resolve it, fail. + if ($write_exception) { + throw new PhutilProxyException( + pht( + 'Unable to write log "%s" to path "%s" because the path is not '. + 'writable.', + $this->getLogName(), + $log_path), + $write_exception); + } + } + + ini_set('error_log', $log_path); + PhutilErrorHandler::setErrorListener(array($this, 'onError')); + } + + public function onError($event, $value, array $metadata) { + // If we've set "error_log" to a real file, so messages won't be output to + // stderr by default. Copy them to stderr. + + if ($this->logPath === null) { + return; + } + + $message = idx($metadata, 'default_message'); + + if (strlen($message)) { + $message = tsprintf("%B\n", $message); + @fwrite(STDERR, $message); + } + } + +}