Ankündigung

Einklappen
Keine Ankündigung bisher.

[Erledigt] Eigene Exceptions mit integriertem Logging

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • [Erledigt] Eigene Exceptions mit integriertem Logging

    Hallo zusammen,

    ich denke gerade über einen Exception-Behandlungs-Mechanismus nach, der ein automatisches Logging beinhaltet.

    Mal das Grundproblem:
    Es geht um eine größere Anwendung, in der Fehler je nach Loglevel in eine Log-Datei geschrieben werden. Um ein bessere Fehleranalyse machen zu können, würde ich gerne eine Basis-Exception entwickeln, die bereits ein integriertes Logging hat und entsprechende Informationen zur Exception in eine "exceptions.log" schreibt, wenn ich den Loglevel auf "Debug" stelle.

    Nun stellt sich die Frage: Wie bekomme ich "den Logger" von außen möglichst elegant und ohne Abhängigkeiten zu erzeugen in meine Exception-Klasse bzw. das Objekt?

    Ich möchte die Basisfunktionalität der Exception eigentlich komplett erhalten, das heißt keine Parameter in den Konstruktor hinzufügen oder weglassen, aber genauso wenig möchte ich beim Werfen einer Exception erst eine Zwischenvariable nutzen, um diese zu werfen.

    Mal ein Beispiel, um zu verdeutlichen, was ich meine:

    PHP-Code:
    <?php
    namespace Andreas;

    // Dummy Logger, eigentlich PSR-3 
    class Logger {
        public function 
    debug($message) {
            echo 
    $message;
        }
    }

    // Hilfsklasse zur Behandlung von Exceptions
    class ExceptionHandler {
        private 
    $logger;
        private static 
    $instance;
        
        private function 
    __construct() {
        }
        
        public static function 
    singleton() {
            if(
    self::$instance === null) {
                 
    self::$instance = new ExceptionHandler();
            }
            return 
    self::$instance;
        }
        
        public function 
    setLogger(Logger $logger) {
            
    $this->logger $logger;
        }
        
        public function 
    handle(\Exception $e) {
            
    $this->logger->debug($e->getTraceAsString());
        }
    }

    // Variante 1: Zusätzlicher Konstruktor-Parameter
    class ExceptionVariante1 extends \Exception
    {
        public function 
    __construct($message=''$code=0Exception $previous nullExceptionHandler $handler=null)
        {
            
    parent::__construct($message$code$previous);
            
            if(
    $handler) {
                
    $handler->handle($this);
            }
        }
    }


    // Variante 2: Mit externen Abhängigkeiten
    class ExceptionVariante2 extends \Exception
    {
        public function 
    __construct($message=''$code=0Exception $previous null)
        {
            
    parent::__construct($message$code$previous);
            
    ExceptionHandler::singleton()->handle($this);
        }
    }


    $logger = new Logger();
    $exceptionHandler ExceptionHandler::singleton();
    $exceptionHandler->setLogger($logger);

    // Variante 1: 4. Parameter => möchte ich eigentlich vermeiden, da die Signatur der Standardexception so bleiben soll, wie sie ist
    try {
        throw new 
    ExceptionVariante1('variante 1'1null$exceptionHandler);
    } catch (
    Exception $e) {
        
    }


    // Variante 2: Eine Abhängigkeit für ExceptionHandler innerhalb der Exception-Klasse => auch nicht sehr elegant
    try {
        throw 
    ExceptionVariante2('variante 2'2);
    } catch (
    Exception $e) {
    }

    // Variante 3: Ohne zusätzliche Exception mit Handler-Zeile im Catch, gefällt mir auch nicht richtig
    try {
        throw new \
    Exception('variante 3'3);
    } catch (
    Exception $e) {
        
    $exceptionHandler->handle($e);
    }
    Der Code ist übrigens nur als Beispiel gedacht, er funktioniert wahrscheinlich nicht.

    Macht mein Vorhaben überhaupt Sinn?
    Weitere Meinungen?
    Tutorials zum Thema Technik:
    https://pilabor.com
    https://www.fynder.de

  • #2
    Hallo,

    ich würde nicht wirklich einen Logger einem Exception Handler übergeben.

    Ich würde eher die Message aus der Exception einem Logger Objekt übergeben. Der Logger soll ja nur Daten schreiben. Eine Exception sollte aber nichts von einem Logger wissen, denke ich, gibt sicher auch Leute die das anders sehen.

    Gruß Litter
    Aus dem Dynamo Lande kommen wir. Trinken immer reichlich kühles Bier. Und dann sind wir alle voll, die Stimmung ist so toll. Aus dem Dynamo Lande kommen wir.
    [URL]http://www.lit-web.de[/URL]

    Kommentar


    • #3
      Spricht was gegen
      PHP-Code:
      class MyException extends Exception {
          protected static 
      $_logger;

          public static function 
      setLogger(Logger $logger) {
              
      self::$_logger $logger;
          }

          public function 
      __construct(/*...*/) {
              if(
      self::$_logger) {
                  
      self::$_logger->log(/*...*/);
              }

              
      parent::__construct(/*...*/);
          }

      ?
      VokeIT GmbH & Co. KG - VokeIT-oss @ github

      Kommentar


      • #4
        PHP-Code:
        try {
            throw new 
        MyException('unknown error');
        } catch (
        MyException $E) {
            
        $Log = new Log();
            
        $Log->write($E->getMessage());

        Wäre etwa mein Ansatz.
        [URL="https://github.com/chrisandchris"]GitHub.com - ChrisAndChris[/URL] - [URL="https://github.com/chrisandchris/symfony-rowmapper"]RowMapper und QueryBuilder für MySQL-Datenbanken[/URL]

        Kommentar


        • #5
          wo belibt da die Konfigurationsmöglichkeit ?

          Heut möcht ich die Messages auf dem Screen,
          morgen in einer log-Datei und im Urlaub per mail.
          Eine if-else-Abfrage nimmt, ordentlich geschrieben eine Menge Platz weg. Platzsparend geht es mit einem ternären Operator.

          Kommentar


          • #6
            Das war nur so die Basismöglichkeit, ich hab keinen Bock über den Mittag auf dem iPhone lange zu schreiben...
            [URL="https://github.com/chrisandchris"]GitHub.com - ChrisAndChris[/URL] - [URL="https://github.com/chrisandchris/symfony-rowmapper"]RowMapper und QueryBuilder für MySQL-Datenbanken[/URL]

            Kommentar


            • #7
              @litterauspirna:
              Ich würde eher die Message aus der Exception einem Logger Objekt übergeben. Der Logger soll ja nur Daten schreiben. Eine Exception sollte aber nichts von einem Logger wissen, denke ich, gibt sicher auch Leute die das anders sehen.
              Die Exception weiß auch im Beispiel nichts von einem Logger. Dafür gibt es ja den ExceptionHandler als Vermittler zwischen Exception und Logger. Der Handler ist eigentlich nur dazu da, das zu verwalten, was mit der Exception nach deren Auslösung passiert. Ob die jetzt geloggt, gedumpt oder konvertiert wird, kann ich im Handler entscheiden. In meinem Fall soll sie geloggt werden...

              @G.Schuster:
              Spricht was gegen...
              Hübsche Idee... das sieht interessant aus, werde ich mal testen, ob das für mich klappt. Vielen Dank.

              @ChristianK:
              Ja, das ginge und wäre meiner Variante 3 sehr ähnlich, würde aber zu viel Code-Redundanz führen, wenn ich immer alle Exceptions im Loglevel "DEBUG" loggen will, weil ich da jedes mal im Catch-Blog eine Zeile habe, die den Log-Eintrag schreiben muss. Außerdem bestünde die Möglichkeit, dass ich es mal aus Unachtsamkeit irgendwo vergesse und kein Log-Eintrag geschrieben wird. Das möchte ich auf jeden Fall vermeiden.

              @Koala:
              wo belibt da die Konfigurationsmöglichkeit ?
              Auf welchen Ansatz bezieht sich dein Post?
              Tutorials zum Thema Technik:
              https://pilabor.com
              https://www.fynder.de

              Kommentar


              • #8
                Auf welchen Ansatz bezieht sich dein Post?
                auf #6 von Christian.

                Ja, das ginge und wäre meiner Variante 3 sehr ähnlich, würde aber zu viel Code-Redundanz führen, wenn ich immer alle Exceptions im Loglevel "DEBUG" loggen will, weil ich da jedes mal im Catch-Blog eine Zeile habe, die den Log-Eintrag schreiben muss. Außerdem bestünde die Möglichkeit, dass ich es mal aus Unachtsamkeit irgendwo vergesse und kein Log-Eintrag geschrieben wird. Das möchte ich auf jeden Fall vermeiden.
                seh ich auch so.

                hab das noch gefunden:
                http://net.tutsplus.com/tutorials/ph...self-with-php/
                Eine if-else-Abfrage nimmt, ordentlich geschrieben eine Menge Platz weg. Platzsparend geht es mit einem ternären Operator.

                Kommentar


                • #9
                  Der Exception-Handler gehört nicht ins Exception-Objekt. Genau dafür hat PHP ja den Exception-Handler Mechanismus geschaffen. Da reicht dann bereits ein gemeinsames Interface, um konkret anfallende Objekte unterscheiden zu können.

                  Ich habe das Thema bspw. in meine Debug-Klasse integriert.
                  [COLOR="#F5F5FF"]--[/COLOR]
                  [COLOR="Gray"][SIZE="6"][FONT="Georgia"][B]^^ O.O[/B][/FONT] [/SIZE]
                  „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                  [URL="http://www.php.de/javascript-ajax-und-mehr/107400-draggable-sorttable-setattribute.html#post788799"][B]Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“[/B][/URL][/COLOR]
                  [COLOR="#F5F5FF"]
                  --[/COLOR]

                  Kommentar


                  • #10
                    Das ist mal so ein kleiner Ansatz wie man das umsetzen kann.

                    PHP-Code:
                    <?php
                    interface IDebugger
                    {
                        public function 
                    writeMessage($msg);
                    }

                    class 
                    JabberDebugger implements IDebugger
                    {
                        public function 
                    writeMessage($msg)
                        {
                            
                        }
                    }

                    class 
                    DisplayDebugger implements IDebugger
                    {
                        public function 
                    writeMessage($msg)
                        {
                            
                        }
                    }

                    class 
                    EmailDebugger implements IDebugger
                    {
                        public function 
                    writeMessage($msg)
                        {
                            
                        }
                    }

                    class 
                    LogDebugger implements IDebugger
                    {
                        public function 
                    writeMessage($msg)
                        {
                            
                        }
                    }

                    class 
                    Debugger
                    {
                        private 
                    $_debuggers;
                        
                        private 
                    $_message;
                        
                        public function 
                    __construct()
                        {
                            
                    $this->_debuggers = new ArrayObject(array());
                        }
                        
                        public function 
                    addDebugger($name)
                        {
                            
                    $this->_debuggers->offsetSet($name$this->_buildDebuggerInstance($name));
                            return 
                    $this;
                        }
                        
                        public function 
                    removeDebugger($name)
                        {
                            
                    $this->_debuggers->offsetUnset($name);
                            return 
                    $this;
                        }
                        
                        public function 
                    removeDebuggers(array $debuggers)
                        {
                            if (
                    false !== is_array($debuggers) && count($debuggers) > 0) {
                                
                                foreach (
                    $debuggers as $debugger) {
                                    
                    $this->_debuggers->offsetUnset($debugger);
                                }
                            }
                        }
                        
                        public function 
                    setMessage($msg)
                        {
                            
                    $this->_message $msg;
                            return 
                    $this;
                        }
                        
                        public function 
                    execute()
                        {
                            foreach (
                    $this->_debuggers as $key => $debugger) {
                                
                    $debugger->writeMessage($this->_message);
                            }
                            
                            return 
                    $this;
                        }
                        
                        public static function 
                    factory()
                        {
                            
                    $debugger = new Debugger();
                            return 
                    $debugger;
                        }
                        
                        protected function 
                    _buildDebuggerInstance($name)
                        {
                            
                    $debugger ucfirst($name) . 'Debugger';
                            
                    $instance = new $debugger();
                            
                            if (!
                    $instance instanceof IDebugger) {
                                throw new 
                    Exception("The debugger class " $debugger " must implements the IDebugger interface.");
                            }
                            
                            return 
                    $instance;
                        }
                    }

                    try {
                        
                    // mache was de machen sollst
                    } catch (Exception $e) {
                        
                    Debugger::factory()->addDebugger('email')
                            ->
                    addDebugger('jabber')
                            ->
                    setMessage($e->getMessage())
                            ->
                    execute();
                    }
                    Du verpackst dort das Logging mit ins Debugging. Damit schlägst du eigentlich 2 Fliegen mit einer Klappe, du kannst Display Meldungen ausgeben, kannst in Log Dateien schreiben, kannst Jabber oder Email senden oder oder oder.

                    Jeder Debugger muss das interface IDebugger implementieren und somit kannst du schon deinen Debugger implementieren.

                    Das Singleton Gedöns würde ich auch weglassen. Das ist an der Stelle vollkommen unnütz, wie Singleton eigentlich allgemein unnütz ist.

                    Setze da liebe auf Dependency Injection. Oder aber so wie in meinem Beispiel ne Factory Method nutzen. Ich verstehe da den Einsatz von Singleton nicht.

                    Gruß Litter
                    Aus dem Dynamo Lande kommen wir. Trinken immer reichlich kühles Bier. Und dann sind wir alle voll, die Stimmung ist so toll. Aus dem Dynamo Lande kommen wir.
                    [URL]http://www.lit-web.de[/URL]

                    Kommentar


                    • #11
                      PHP-Code:
                      try {
                          
                      // mache was de machen sollst
                      } catch (Exception $e) {
                          
                      Debugger::factory()->addDebugger('email')
                              ->
                      addDebugger('jabber')
                              ->
                      setMessage($e->getMessage())
                              ->
                      execute();

                      da mußt doch jetzt wieder überall, bei jeder Exception einen Debugger instanzieren.

                      würde aber zu viel Code-Redundanz führen, wenn ich immer alle Exceptions im Loglevel "DEBUG" loggen will, weil ich da jedes mal im Catch-Blog eine Zeile habe, die den Log-Eintrag schreiben muss. Außerdem bestünde die Möglichkeit, dass ich es mal aus Unachtsamkeit irgendwo vergesse und kein Log-Eintrag geschrieben wird. Das möchte ich auf jeden Fall vermeiden.
                      Eine if-else-Abfrage nimmt, ordentlich geschrieben eine Menge Platz weg. Platzsparend geht es mit einem ternären Operator.

                      Kommentar


                      • #12
                        Zitat von Koala Beitrag anzeigen
                        PHP-Code:
                        try {
                            
                        // mache was de machen sollst
                        } catch (Exception $e) {
                            
                        Debugger::factory()->addDebugger('email')
                                ->
                        addDebugger('jabber')
                                ->
                        setMessage($e->getMessage())
                                ->
                        execute();

                        da mußt doch jetzt wieder überall, bei jeder Exception einen Debugger instanzieren.
                        Naja nicht zwingend, dass ist ja nur das Konstrukt mehr nicht. Die Anwendung dessen kannst du auch wieder anders steuern. Das zeigt nur wie man den Debugger umsetzen kann, nicht muss.

                        Das kannst du auch dann anwenden wenn du andere Sachen loggen willst die nichts mit Exceptions zu tun haben. Es gibt auch Log Meldungen wo keine Exceptions auftreten z.B.

                        Den Debugger kann man auch in einem Service Locator verarbeiten.

                        würde aber zu viel Code-Redundanz führen, wenn ich immer alle Exceptions im Loglevel "DEBUG" loggen will, weil ich da jedes mal im Catch-Blog eine Zeile habe, die den Log-Eintrag schreiben muss. Außerdem bestünde die Möglichkeit, dass ich es mal aus Unachtsamkeit irgendwo vergesse und kein Log-Eintrag geschrieben wird. Das möchte ich auf jeden Fall vermeiden.
                        Nun für Unachtsamkeiten eines Programmierers kann keiner was. Ein bisschen konzentriert muss man schon arbeiten.
                        Ich setze so etwas ein auf verschiedenste Art und weisen, habe mir so etwas auch in Java geschrieben und bin ganz zufrieden damit.
                        Aus dem Dynamo Lande kommen wir. Trinken immer reichlich kühles Bier. Und dann sind wir alle voll, die Stimmung ist so toll. Aus dem Dynamo Lande kommen wir.
                        [URL]http://www.lit-web.de[/URL]

                        Kommentar


                        • #13
                          @litterauspirna:
                          Nett, aber ich glaube du erfindest das Rad neu (siehe log4php / log4j / log4js / log4...)
                          Nun für Unachtsamkeiten eines Programmierers kann keiner was. Ein bisschen konzentriert muss man schon arbeiten.
                          Wenn man im Team arbeitet, kann man oft selbst so aufmerksam sein, wie man will und es bringt nix Es bleibt aber immer noch das Redundanz-Argument.

                          @nikosch:
                          Der Exception-Handler gehört nicht ins Exception-Objekt.
                          Genau da liegt mein Problem. Ich weiß das... aber ich frage mich, ob hier "Faulheit" nicht vor "Eleganz" kommt, denn ich spare so bei jeder Exception eine Zeile !redundanten! Code und bin sicher, dass es immer gleich abläuft. Nochmal die Möglichkeiten die mir einfallen:
                          PHP-Code:
                          // 1. Logging wird im Konstruktor mit übergeben (4 Zeilen)
                          // Nachteile: Signatur der Standard Exception wird geändert und der logger muss jedes mal übergeben werden (unbequem)
                          try {
                              throw new 
                          Exception('test'1null$logger);
                          } catch (
                          Exception $e) {
                          }

                          // 2. Logging wird im Catch-Block durchgeführt (5 Zeilen)
                          // Nachteil: Redundanter Code im Catch Block für jede Exception
                          try {
                              throw new 
                          Exception('test'1);
                          } catch (
                          Exception $e) {
                              
                          $logger->debug($e->getMessage());
                          }

                          // 3. Zwischenvariable für die Exception (6 Zeilen)
                          // Nachteil: Ist syntaktischer Unsinn und 2 Zeilen redundanter code pro Aufruf
                          try {
                              
                          $e = new Exception('test'1);
                              
                          $e->performLogAction($logger);
                              throw 
                          $e;
                          } catch (
                          Exception $e) {
                          }

                          // 4. G.Schusters Lösung, statische Methode zum Speichern des $loggers für Exceptions (3 Zeilen + einmal 1 für die Konfiguration)
                          // Nachteile: ExceptionHandler / Logger gehört streng genommen nicht in die Exception-Klasse => kleiner Eleganzfail
                          Exception::setLogger($logger); // Nur einmal im Script!
                          try {
                              throw new 
                          Exception('test'1null);
                          } catch (
                          Exception $e) {

                          Genau dafür hat PHP ja den Exception-Handler Mechanismus geschaffen.
                          Ich suche eine elegante Möglichkeit, alle in der gesamten Anwendung auftretenden Exceptions zu loggen und trotzdem Redundanz, eine Modifikation der Standard-Exception-Signatur und umständliches Werfen von Exceptions (über eine Zwischenvariable) zu vermeiden. Bisher gefällt mir da der Ansatz von G.Schuster am besten und er ist auch "elegant" genug, weil man den ExceptionHandler in der Exception nicht setzen muss, sondern kann. Da man den Logger / ExceptionHandler von außen übergibt, ist man auch sehr flexibel und schafft keine unnötigen Abhängigkeiten. Habs mal getestet und es funktioniert für mich eigentlich ganz gut. Sehr wenig Overhead und "Abwärtskompatibel" zur normalen Exceptions. Das würde ich nur gegen eine noch elegantere und sparsamere Methodik eintauschen

                          Da reicht dann bereits ein gemeinsames Interface, um konkret anfallende Objekte unterscheiden zu können.
                          Verstehe ich nicht ganz. Was meinst du damit?

                          Ich habe das Thema bspw. in meine Debug-Klasse integriert.
                          Das bedeutet aber dann wieder eine zusätzliche Zeile pro Exception. Wäre trotzdem auf ein Beispiel gespannt.
                          Tutorials zum Thema Technik:
                          https://pilabor.com
                          https://www.fynder.de

                          Kommentar


                          • #14
                            Exception mit angebundenem Logger geht im übrigen schon der "Nur eine Aufgabe pro Klasse" gegen den Strich.

                            Logging ist Teil des Anwendungsablaufs und wird je nach Environment wohl konfiguriert. Sauberes Logging ohne quadrilliones erneutes Notieren einer Instanzbeschaffung des Loggers in jeden Catch oder Final geht wohl über Events am saubersten. Andernfalls ist es auch nicht wirklich schwierig einen globalen Exception-Handler einzusetzen, der das in dem Fall loggt. Denn Exceptions sind Kritische Ausnahmen, in der die Anwendung aus eigener Kraft nicht mehr weiter weiß.
                            [URL="https://gitter.im/php-de/chat?utm_source=share-link&utm_medium=link&utm_campaign=share-link"]PHP.de Gitter.im Chat[/URL] - [URL="https://raindrop.io/user/32178"]Meine öffentlichen Bookmarks[/URL] ← Ich habe dir geholfen ? [B][URL="https://www.amazon.de/gp/wishlist/348FHGUZWTNL0"]Beschenk mich[/URL][/B].

                            Kommentar


                            • #15
                              geht wohl über Events am saubersten
                              Kannst du hierzu ein Beispiel geben? Müsste ja auch von einem Handler behandelt werden. Mein ExceptionHandler aus dem Beispiel wäre ja schon so etwas ähnliches wie ein EventHandler, nur eben sehr rudimentär. Wäre das etwa in der Art zu verstehen?

                              PHP-Code:
                              class Exception implements EventInterface {
                                  protected static 
                              $listeners = array();
                                  
                                  public function 
                              __construct($message=''$code=0Exception $previous null) {
                                      
                              parent::__construct($message$code$previous);
                                      
                              $this->notifyListeners();
                                  }
                                  
                                  public static function 
                              addListener(EventListenerInterface $listener) {
                                      
                              self::$listeners[] = $listener;
                                  }
                                  
                                  protected function 
                              notifyListeners() {
                                      foreach(
                              self::$listeners as $listener) {
                                          
                              $listener->raiseEvent($this);
                                      }
                                  }

                              Tutorials zum Thema Technik:
                              https://pilabor.com
                              https://www.fynder.de

                              Kommentar

                              Lädt...
                              X