diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Kolab/Utils/DAVLogger.php | 175 | ||||
-rw-r--r-- | lib/Kolab/Utils/HTTPResponse.php | 92 |
2 files changed, 232 insertions, 35 deletions
diff --git a/lib/Kolab/Utils/DAVLogger.php b/lib/Kolab/Utils/DAVLogger.php index 982d87e..311fc8a 100644 --- a/lib/Kolab/Utils/DAVLogger.php +++ b/lib/Kolab/Utils/DAVLogger.php @@ -23,7 +23,9 @@ namespace Kolab\Utils; +use \rcube; use Sabre\DAV; +use Kolab\DAV\Auth\HTTPBasic; /** @@ -31,8 +33,24 @@ use Sabre\DAV; */ class DAVLogger extends DAV\ServerPlugin { + const CONSOLE = 1; + const HTTP_REQUEST = 2; + const HTTP_RESPONSE = 4; + + private $rcube; private $server; private $method; + private $loglevel; + + + /** + * Default constructor + */ + public function __construct($level = 1) + { + $this->rcube = rcube::get_instance(); + $this->loglevel = $level; + } /** * This initializes the plugin. @@ -40,47 +58,134 @@ class DAVLogger extends DAV\ServerPlugin * * @param Server $server */ - public function initialize(DAV\Server $server) - { - $this->server = $server; + public function initialize(DAV\Server $server) + { + $this->server = $server; - $server->subscribeEvent('beforeMethod', array($this, '_beforeMethod')); - $server->subscribeEvent('exception', array($this, '_exception')); - $server->subscribeEvent('exit', array($this, '_exit')); + $server->subscribeEvent('beforeMethod', array($this, '_beforeMethod'), 15); + $server->subscribeEvent('exception', array($this, '_exception')); + $server->subscribeEvent('exit', array($this, '_exit')); + + // replace $server->httpResponse with a derived class that can do logging + $server->httpResponse = new HTTPResponse(); } - /** - * Handler for 'beforeMethod' events - */ - public function _beforeMethod($method, $uri) - { - $this->method = $method; + /** + * Handler for 'beforeMethod' events + */ + public function _beforeMethod($method, $uri) + { + $this->method = $method; - // log to console - console($method . ' ' . $uri); - } + // turn on per-user http logging if the destination file exists + if ($this->loglevel < 2 && $this->rcube->config->get('kolabdav_user_debug', false) + && ($log_dir = $this->user_log_dir()) && file_exists($log_dir . '/httpraw')) { + $this->loglevel |= (self::HTTP_REQUEST | self::HTTP_RESPONSE); + } - /** - * Handler for 'exception' events - */ - public function _exception($e) - { - // log to console - console(get_class($e) . ' (EXCEPTION)', $e->getMessage() /*, $e->getTraceAsString()*/); - } + // log full HTTP request data + if ($this->loglevel & self::HTTP_REQUEST) { + $request = $this->server->httpRequest; + $content_type = $request->getHeader('CONTENT_TYPE'); + if (strpos($content_type, 'text/') === 0) { + $http_body = $request->getBody(true); - /** - * Handler for 'exit' events - */ - public function _exit() - { - $time = microtime(true) - KOLAB_DAV_START; + // Hack for reading php:://input because that stream can only be read once. + // This is why we re-populate the request body with the existing data. + $request->setBody($http_body); + } + else if (!empty($content_type)) { + $http_body = '[binary data]'; + } - if (function_exists('memory_get_usage')) - $mem = round(memory_get_usage() / 1024) . 'K'; - if (function_exists('memory_get_peak_usage')) - $mem .= '/' . round(memory_get_peak_usage() / 1024) . 'K'; + // catch all headers + $http_headers = array(); + foreach (apache_request_headers() as $hdr => $value) { + $http_headers[$hdr] = "$hdr: $value"; + } - console(sprintf("/%s: %0.4f sec; %s", $this->method, $time, $mem)); - } + $this->write_log('httpraw', $request->getMethod() . ' ' . $request->getUri() . ' ' . $_SERVER['SERVER_PROTOCOL'] . "\n" . + join("\n", $http_headers) . "\n\n" . $http_body); + } + + // log to console + if ($this->loglevel & self::CONSOLE) { + $this->write_log('console', $method . ' ' . $uri); + } + } + + /** + * Handler for 'exception' events + */ + public function _exception($e) + { + // log to console + $this->console(get_class($e) . ' (EXCEPTION)', $e->getMessage() /*, $e->getTraceAsString()*/); + } + + /** + * Handler for 'exit' events + */ + public function _exit() + { + if ($this->loglevel & self::CONSOLE) { + $time = microtime(true) - KOLAB_DAV_START; + + if (function_exists('memory_get_usage')) + $mem = round(memory_get_usage() / 1024) . 'K'; + if (function_exists('memory_get_peak_usage')) + $mem .= '/' . round(memory_get_peak_usage() / 1024) . 'K'; + + $this->write_log('console', sprintf("/%s: %0.4f sec; %s", $this->method, $time, $mem)); + } + + // log full HTTP reponse + if ($this->loglevel & self::HTTP_RESPONSE) { + $this->write_log('httpraw', "RESPONSE: " . $this->server->httpResponse->dump()); + } + } + + /** + * Wrapper for rcube::cosole() to write per-user logs + */ + public function console(/* ... */) + { + if ($this->loglevel & self::CONSOLE) { + $msg = array(); + foreach (func_get_args() as $arg) { + $msg[] = !is_string($arg) ? var_export($arg, true) : $arg; + } + + $this->write_log('console', join(";\n", $msg)); + } + } + + /** + * Wrapper for rcube::write_log() that can write per-user logs + */ + public function write_log($filename, $msg) + { + // dump data per user + if ($this->rcube->config->get('kolabdav_user_debug', false)) { + if ($this->user_log_dir()) { + $filename = HTTPBasic::$current_user . '/' . $filename; + } + else { + return; // don't log + } + } + + rcube::write_log($filename, $msg); + } + + /** + * Get the per-user log directory + */ + private function user_log_dir() + { + $log_dir = $this->rcube->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); + $user_log_dir = $log_dir . '/' . HTTPBasic::$current_user; + + return HTTPBasic::$current_user && is_writable($user_log_dir) ? $user_log_dir : false; + } }
\ No newline at end of file diff --git a/lib/Kolab/Utils/HTTPResponse.php b/lib/Kolab/Utils/HTTPResponse.php new file mode 100644 index 0000000..4f4e72d --- /dev/null +++ b/lib/Kolab/Utils/HTTPResponse.php @@ -0,0 +1,92 @@ +<?php + +/** + * Utility class representing a HTTP response with logging capabilities + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace Kolab\Utils; + +/** + * This class represents a HTTP response. + */ +class HTTPResponse extends \Sabre\HTTP\Response +{ + private $status; + private $body = ''; + private $headers = array(); + + /** + * Sends an HTTP status header to the client. + * + * @param int $code HTTP status code + * @return bool + */ + public function sendStatus($code) + { + $this->status = $this->getStatusMessage($code, $this->defaultHttpVersion); + return parent::sendStatus($code); + } + + /** + * Sets an HTTP header for the response + * + * @param string $name + * @param string $value + * @param bool $replace + * @return bool + */ + public function setHeader($name, $value, $replace = true) { + $this->headers[$name] = $value; + return parent::setHeader($name, $value, $replace); + } + + /** + * Sends the entire response body + * + * This method can accept either an open filestream, or a string. + * + * @param mixed $body + * @return void + */ + public function sendBody($body) + { + if (is_resource($body)) { + fpassthru($body); + $this->body = '[binary data]'; + } + else { + echo $body; + $this->body .= $body; + } + } + + /** + * Dump the response data for logging + */ + public function dump() + { + $result_headers = ''; + foreach ($this->headers as $hdr => $value) { + $result_headers .= "\n$hdr: " . $value; + } + + return $this->status . $result_headers . "\n\n" . $this->body; + } +}
\ No newline at end of file |