Logowanie błędów jest kluczowym elementem minitoringu aplikacji. Przekonał się o tym chociażby każdy programista, który pracuje w firmie oferującej dodatkowo dla swoich produktów terminowy support.

Dzięki temu, że błędy są logowane jesteśmy w stanie szybko i czasami transparentnie wyeliminować bugi w aplikacji, tak że klient nawet się nie dowie o ich istnieniu:)

Jeśli Zend Framework jest podstawą twojego projektu oferuję Tobie bardzo wygodny i przejrzysty sposób na logowanie błędów aplikacji.

Potrzebna będzie jedna klasa, ponieważ aż się prosi aby system logowania błędów był ładowany jako resource, przez Zend_Application. Zarządzanie zasobów zaprezentowane w Zend Framework jest moim zdaniem rewelacyjne, ponieważ bardzo łatwo możemy wydzielić zasoby, dzięki czemu mamy wprowadzoną dodatkową  logikę i ład w naszej aplikacji,  ponadto  dowolny zasób  można łatwo i sprawnie włączyć/wyłączyć.

Przykładowa klasa mogłaby wygłądać następująco:

class Zextend_Application_Resource_Log extends Zend_Application_Resource_ResourceAbstract
{
 
    public function init()
    {
        /**
         * Pobranie głównej konfiguracji.
         */
        $config = $this->getBootstrap()->getContainer()->config;
 
        /**
         * Przygotowanie formatu zapisanej informacji.
         *
         * @example Postać zmiennej $config->format:
         * "%timestamp% %priorityName%: %message%"
         */
        $format = new Zend_Log_Formatter_Simple($config->format . PHP_EOL);
 
        /**
         * Sprawdzanie, ktory sposób logowania błędów został włączony.
         *
         * @example Postać zmiennej $config->log->stream:
         * "../temporary/log/error.log" 
         */
        $logger = new Zend_Log();
        if($config->log->file) {
           $logger->addWriter(
                   $this->_getStreamWriter($format, $config->log->stream));
        }
 
        if($config->log->stdoutput) {
           $logger->addWriter(
                   $this->_getStreamWriter($format, 'php://output'));
        }
 
        if($config->log->firebug) {
           $logger->addWriter(
                   $this->_getFirebugWriter($format));
        }
        if($config->log->mail) {
            $logger->addWriter($this->_getMailWriter($config));
        }
 
        /**
         * Używamy tego logger wszędzie w aplikacji.
         */
        Zend_Registry::set( 'log', $logger );
        return $logger;
    }
    /**
     * Zwraca obiekt zapisania informacja do Firebug.
     * 
     * @param Zend_Log_Formatter_Simple $format
     * @return Zend_Log_Writer_Firebug
     */
    private function _getFirebugWriter(Zend_Log_Formatter_Simple $format)
    {
        $writer = new Zend_Log_Writer_Firebug();
        $writer->setFormatter($format);
 
        return $writer;
    }
    /**
     * Zwraca obiekt zapisania informacji w postaci emaila.
     *
     * @param Zend_Config_Ini $config
     * @return Zend_Log_Writer_Mail
     */
    private function _getMailWriter(Zend_Config_Ini $config)
    {
        $mail = new Zend_Mail();
        $mail->setDefaultTransport(new Zend_Mail_Transport_Smtp());
 
        $mail->setFrom($config->email->from, $config->email->fromAlias);
        $mail->addTo($config->app->admin->email);
        $mail->addTo($config->app->webmaster->email);
        $mail->setSubject($config->app->name . ' - nastapil nieoczekiwany blad!');
 
        $writer = new Zend_Log_Writer_Mail($mail);
        $writer->addFilter(Zend_Log::WARN);
 
        return $writer;
    }
    /**
     * Zwraca obiekt zapisania informacji do PHP stream.
     * 
     * @param Zend_Log_Formatter_Simple $format
     * @param string $stream
     * @return Zend_Log_Writer_Stream
     */
    private function _getStreamWriter(Zend_Log_Formatter_Simple $format, $stream = '')
    {
        $writer = new Zend_Log_Writer_Stream($stream);
        $writer->setFormatter($format);
 
        return $writer;
    }
}

Jak widać oferuje ona możliwość logowania błędów na kilka sposobów: streaming do pliku, badź na ekran, JSON’em prosto do firebug’a (warunkiem odczytu jest zainstalowanie wtyczki FirePHP w Firefox) lub powiadomienie na email. W przykładzie część konfiguracji znajduje się w pliku /application/configs/application.ini, ktory ładowany jest poprzez Zend_Config_Ini do rejestru. Odczytany zostaje między innymi email do webmastera oraz do admina, czyli osób odpowiedzialnych za poprawne działanie aplikacji, ponadto zdefiniowane są takie parametry jak scieżka do pliku  z blędami (error.log) czy też format komunikatu z błędem.

Oczywiście trzeba tę funkcjonalność dołączyć  do projektu jako  nowy zasób. Nic prostszego – w pliku konfiguracyjnym /application/configs/application.ini dodajemy kolejny resource do listy:

resources.types      = "array()"
resources.view       = "array()"
resources.navigation = "array()"
resources.log        = "array()"

Dodatkowo musimy zdefniować ustawienia, które załączają odpowiednei przekazanie informacji o błędzie. Czyli znów w /application/configs/application.ini dopisujemy:

log.file      = true
log.firebug   = true
log.mail      = true
log.stdoutput = true

Rzecz jasna korzystamy z odpowiedniego typu logowania w zależności od potrzeb. Bardzo wygodnie jest w trakcie tworzenia kodu, czy też jego testowania, otrzymywać komunikaty do konsoli FF, wykorzystując firePHP, dodatkowo wygodne jest odczytywanie błędów z pliku error.log – otwieramy konsole i  dajemy polecenie:

# tail -f error.log

i mamy nasz system monitorujacy, w którym dokładnie zapisane jest gdzie wystąpił nieoczekiwany błąd (kupujemy telewizor LCD, który pokazuje te komunikaty przez cały czas 🙂 oczywiście żartuje :)).  Jeśli aplikacja jest już w wersji produkcyjnej to bardzo fajne jest rozwiązanie z przychodzącym emailem – można wtedy szybko interweniować.

Pamiętajmy jednak, że jeśli aplikacja jest już na produkcji, należy zablokować logowanie błędów do FireBug’a!!

Logowane błędy są tylko dla nas – developerów, adminów, …,  i mają stanowić wskazówkę do szybkiego ich  rozwiązania. Jeśli te informacje trafią do niepowołanych rąk to niestety możemy wskazać najsłabsze ogniwo naszej aplikacji, a tym samym stworzyć okazję do włamania się do niej np. hackerowi.

Kolejną sprawą jest miejsce, w którym nasz logger, trzymany w rejestrze, może dokonać zapisu błędu. Jednym z lepszym miejsc jest kontroler ErrorController.php i w nim metoda errorAction,  na którą domyślnie przekierowany jest błąd. Oczywiście gdzie będzie odbywało się logowanie błędów utożsamiony z tą linijką kodu:

if (Zend_Registry::isRegistered('log')) {
            $logger = Zend_Registry::get('log');
            if (!is_null($this->_error)) {
                $logger->log(
                    $this->_error->exception->getMessage() . "\n\n" .
                    $this->_error->exception->getTraceAsString(),
                    Zend_Log::ERR
                );
            }
        }

jest uzależnione od  potrzeb aplikacji i zaprojektowania jej przez programistę.

Taki sposób logowania błędów  można implementować w każdym projekcie – nie tylko opartym o Zend Framework. Po drobnych modyfikacjach, korzystając tylko z kilku komponentów (w wersji ZF 1.10.6), Log(20 plików), Wildfire (8 plików),  Json (14 plików), Config (8 plików) + Exception.php, można stworzyć taki sam system logowania błędów.