op welk punt exception afhandelen?
Stel je hebt een kernel. De kernel geeft de configurator opdracht om een paar instellingen te configureren. De configurator geeft vervolgens de cacher opdracht om deze instellingen op te slaan. De cacher geeft het filesystem opdracht om de instellingen in een bestand op te slaan.
Schematisch:
Nu gaat er in het filesystem iets mis. Het bestand kan niet worden opgeslagen omdat de map-rechten verkeerd zijn ingesteld. Het filesystem gooit een exception. Deze wordt opgevangen door de cacher. Deze gooit ook een exception met als previous exception de filesystem exception. De configurator vangt de cacher exception op.
En nu? Ik vraag me af wat er nu moet gebeuren. Ik denk niet dat je het probleem van de verkeerde bestandsrechten via de code kunt oplossen, maar je zult wel moeten loggen dat er iets misgaat. Maar op welk punt doe je dat?
Situatie A)
De configurator vangt de cacher exception op en logt deze.
OF
Situatie B)
De configurator gooit ook weer een exception en deze exception wordt in de kernel opgevangen en vervolgens gelogd.
Dus situatie A)
- filesystem gooit filesystem exception
- cacher vangt filesystem exception en gooit cacher exception met de filesystem exception als previous exception
- configurator vangt de cacher exception en logt deze
of situatie B)
- filesystem gooit filesystem exception
- cacher vangt filesystem exception en gooit cacher exception met de filesystem exception als previous exception
- configurator vangt de cacher exception en gooit een configurator exception met de cacher exception als previous exception
- kernel vangt de configurator exception en logt deze
Kiezen jullie voor situatie A of B en waarom?
Ik heb zelf een lichte voorkeur voor A, omdat het configureren op zich wel goed gaat. Het cachen gaat fout, en de opdracht voor het cachen wordt in de configurator gegeven. Je zou dan kunnen zeggen dat je ook vanuit de configurator de fout moet loggen. Maar ga je het "breder" trekken, dan zou je kunnen zeggen dat er ergens tijdens het configureren iets is fout gegaan, en dat je om die reden een configurator exception gooit. Wat is wijs?
Gewijzigd op 19/11/2013 03:03:13 door Ozzie PHP
Er is niks tijdens het comfigureren misgegaan. Alleen tijdens het opslaan. Het is een fout waar je programma gewoon om heen kan werken en dus is het nutteloos om je hele applicatie plat te gooien.
Wouter J op 19/11/2013 07:49:52:
Er is niks tijdens het comfigureren misgegaan. Alleen tijdens het opslaan. Het is een fout waar je programma gewoon om heen kan werken en dus is het nutteloos om je hele applicatie plat te gooien.
Inderdaad, bijvoorbeeld een throw new FileSystem\Exception('Insufficient disk space') heeft voor de ene toepassing andere gevolgen dan voor de andere. Dat los je op in een try/catch.
Verder zou ik het loggen van exceptions wat meer loskoppelen van de exception handling. In je voorbeelden log je de FileSystem-exception pas een paar klassen dieper. Vergeet je dat of log je te weinig, dan gaat ware aard van de fout aan je voorbij. Achter de Cacher-fout gaat een FileSystem-probleem schuil; dát wil je kunnen terugvinden. Bovendien kan hetzelfde FileSystem-probleem elders andere problemen veroorzaken, bijvoorbeeld bij het archiveren van e-mail of het uploaden van afbeeldingen. Op systeemniveau wil je weten welke systeemonderdeel faalt.
Als je "verantwoordelijkheden" nog ruimer neemt, belanden andere soorten fouten in een organisatie op een ander bureau. Dat heeft dus mogelijk consequenties voor hoe je het gebruik van de logs inricht.
>> Er is niks tijdens het comfigureren misgegaan. Alleen tijdens het opslaan. Het is een fout waar je programma gewoon om heen kan werken en dus is het nutteloos om je hele applicatie plat te gooien.
Oke, dat is ook mijn gedachtengang.
>> Verder zou ik het loggen van exceptions wat meer loskoppelen van de exception handling. In je voorbeelden log je de FileSystem-exception pas een paar klassen dieper. Vergeet je dat of log je te weinig, dan gaat ware aard van de fout aan je voorbij.
Ward, dit vind ik een hele interessante opmerking. Hoe ik het in eerdere gesprekken heb begrepen, zou het in dit geval juist logisch zijn om de cacher exception te loggen. Je zegt dan WAT er is misgegaan, namelijk "het cachen is mislukt". Als het goed is bevat de cacher exception dan weer een previous exception waarin vermeld staat dat er een fout is opgetreden in het filesystem.
De totale melding wordt dus: Let op, er is iets fout gegaan met cachen -> Er is iets niet goed gegaan met het opslaan van bestand foo.
Ik dacht dat dit de bedoeling was?
Nee, dat is voor debuggen. (dus tonen op scherm) Voor het loggen zou ik ook de fout loggen voordat je de exception gooit (wanneer je de exception belangrijk genoeg vind om te loggen).
Ik dacht dat het als volgt werkte, maar blijkbaar zit ik er nu naast:
Filesystem gooit een exception. Aan die exception geef ik een $message mee "filesystem could not save file blabla". De cacher vangt deze exception op en gooit een cacher exception met als $message "cacher could not cache file blabla". De cacher exception krijgt als previous exception de filesystem exception mee.
Vervolgens wordt de cacher exception opgevangen door de configurator. Dit is dus het punt waarop het eigenlijk fout is gegaan. Namelijk de configurator wil iets cachen en dat is niet gelukt. Ik had dan ook het idee om op dit punt, waar de fout optreedt, te bepalen wat ik ermee ga doen. In dit geval dus loggen. In de log komt dan zoiets te staan: 20131109 14:12 configurator could not cache file blabla. cacher could not cache file blabla. filesystem could not save file blabla
Aan jullie laatste reacties te horen, klopt dit niet helemaal. Maar hoe hoort het dan wel?
Gewijzigd op 19/11/2013 14:14:14 door Ozzie PHP
Stel, je hebt A -> B -> C voor een operatie. In A treedt een probleem op, maar B weet het te omzeilen. Dan is er wat C betreft geen probleem meer.
Voorbeeldje. Met een cURL-verzoek (mijn B) haal ik data op bij een externe server (A). Die data veranderen echter niet vaak. Mislukt het cURL-verzoek, meestal doordat de externe service er uit ligt, dan gebruik ik oudere data uit een cache. Applicaties die de data vervolgens gebruiken (C) hoeven dat niet te weten.
Heel letterlijk dus: deze exception is een uitzondering, niet per se een fout en liever helemaal geen fatale fout.
In mijn voorbeeld met het cachen, is het op zich niet zo erg dat het cachen mislukt. De applicatie kan gewoon doorgaan, alleen de gegevens moeten telkens "on the fly" geconfigureerd worden in plaats van dat ze uit het cache-bestand komen. Geen onoverkomelijk probleem weliswaar, maar ik wil er wel (via een logbericht) van op de hoogte worden gebracht. Ik dacht dat dus te doen op de manier zoals ik in mijn bericht hierboven precies heb omschreven. Ik ben benieuwd wat daar dan niet juist aan is, of wat er beter kan.
Door het loggen in de FileSystem te doen heb je hetzelfde effect.
Loggen zou ik in beide klassen doen.
Je kunt overigens altijd wel loggen, alleen wil je niet van elke log een e-mailbericht maken. Ik heb je geloof ik wel eens verteld dat ik een 403 logde. Dat wil je niet: dan komen alle activiteiten van een firewall bovendrijven.
Gewijzigd op 19/11/2013 15:07:55 door Ward van der Put
Oké, maar als ik me niet vergis zeiden jullie eerder dat het afhandelen van een exception + het loggen/e-mailen pas gebeurt op de plek waar je de exception opvangt. Want in het ene geval wil je iets wel loggen, en in het andere geval niet. Dat lijkt elkaar nu tegen te spreken.
Stel ik wil een bestand cachen, maar het filesystem kan het bestand niet opslaan omdat de map-rechten van de cache-directory verkeerd zijn ingesteld. Op zich best vervelend, maar de applicatie kan gewoon doorwerken. Echter, als alle klanten ineens geen facturen kunnen opslaan, is het probleem van een heel andere orde en moet er andere actie worden ondernomen. Niet het filesystem bepaalt dan wat er moet gebeuren, maar de aanroepende class(es). Tenminste, zo heb ik het eerder van jullie begrepen.
>> Dan heb je dus een throw in het FileSystem en een catch in de Cacher. Kan de Cacher de exception afvangen, dan is het probleem opgelost. Kan de Cacher dat niet, dan doe je opnieuw een throw. Je kunt eventueel nog in een catch "re-throwen" met throw $e als je wilt dat de FileSystem-exception boven komt.
De cacher kan het probleem niet oplossen en zal dus een exception gooien. Die vang ik dan op in de configurator. In feite kan deze het probleem ook niet oplossen, maar zal wel de exception loggen, zodat ik zelf in actie kan komen om te kijken wat er aan de hand is en het probleem op te lossen.
Ward van der Put op 19/11/2013 10:55:09:
Inderdaad, bijvoorbeeld een throw new FileSystem\Exception('Insufficient disk space') heeft voor de ene toepassing andere gevolgen dan voor de andere. Dat los je op in een try/catch.
Hoe kun je trouwens vaststellen of je schijf vol is?
Om bij het voorbeeld te blijven: als je nog 5 MB over hebt, kan je nog heel wat zaken cachen, maar gaat het opslaan van een PDF van 10 MB niet lukken. Met andere woorden, dezelfde situatie eindigt niet altijd in dezelfde exception.
Je zou bijvoorbeeld exceptions van verschillende niveaus kunnen onderscheiden. Je krijgt dan als het ware een matrix met soorten exception (FileSystemException) en de ernst van de situatie (fatal FileSystemException, recoverable FileSystemException, enzovoort). De PSR-3 Logger Interface kent een leuk rijtje niveaus, tot en met de "emergency" voor "system is unusable".
Als het niet slagen uit eindelijk tot een fout leid die gelogd moet worden (merk op, een fout geen exception) dan zal het ook gelogd worden.
Dus je krijgt dan in je log file zeg maar:
Code (php)
1
2
3
2
3
[2013-11-19 15:08:30] core.DEBUG: FileSystem - updated file "cache019dy97eqr.inc"
[2013-11-19 15:18:34] core.DEBUG: Filesystem - could not update file "cache019dy97eqr.inc": "Insufficient disk space"
[2013-11-19 15:18:34] core.ERROR: Cacher - could not cache routes
[2013-11-19 15:18:34] core.DEBUG: Filesystem - could not update file "cache019dy97eqr.inc": "Insufficient disk space"
[2013-11-19 15:18:34] core.ERROR: Cacher - could not cache routes
Edit:
Merk op 2: De DEBUG logs zullen alleen in dev. omgeving worden gelogd. In prod. omgeving zou ik dan werken met een finger-crossed handler, waardoor deze pas worden gelogd wanneer de cacher een error logt.
Gewijzigd op 19/11/2013 17:16:04 door Wouter J
Oké, ik begrijp je. Als je dat dan terugbrengt naar mijn geschetste situatie, dan zou de cacher dus geen exception gooien, maar de class die de pdf van 10mb opslaat wel. Dat is dan in feite toch prima?
@Wouter: ah oké. Grappig dat je alles logt. Waarom doe je dat? Dingen die fout gaan begrijp ik, maar waarom log je ook zaken die goed gaan?
Toevoeging op 19/11/2013 17:20:06:
P.S.
@Ward... dank voor de disk_free_space funtie, maar hoe bepaal je dan in de praktijk dat het opslaan is mislukt vanwege te weinig schijfruimte?
Helpt je erg bij het debuggen. Dan weet je tot hoever je applicatie nog goed ging en je kan eventueel al zien wat er verkeerd gaat (bijv. verkeerde bestand wordt gecached, etc.). Zoals ik in mijn edit al zei (die je mogelijk niet had gelezen) worden deze DEBUG dingen normaal gesproken niet gelogd wanneer alles goed gaat in de prod. omgeving.
Kunnen jullie aub nog even reageren op mijn vraag hierboven... http://www.phphulp.nl/php/forum/topic/op-welk-punt-exception-afhandelen/93003/#666355
Klopt het nu wat ik doe, of niet? Het lijkt me namelijk wel een handige manier.
Je kunt in een multi-processing omgeving namelijk zoiets verwachten:
1. Applicatie X vraagt via een if aan FileSystem of er genoeg ruimte is.
2. FileSystem antwoordt met (bool) ja of (int) 10 MB.
3. Applicatie Y knalt een upload van 5 MB op dezelfde schijf.
4. Applicatie X heeft nu misschien een probleem...
Als je logt zoals Wouter aangeeft, kun je dit terugvinden: oh het was onschuldig toeval, want er kwam net een bestand van 5 MB binnen. Gebeurt een keer per halfjaar, laat maar overwaaien.
Lees die PSR-3 verder even: daarin worden concrete noodgevallen van het type "stuur nu acuut een sms naar Ozzie" uitgewerkt. Bijna letterlijk :)
Kom ik wel gelijk bij een volgende vraag ...
>> 1. Applicatie X vraagt via een if aan FileSystem of er genoeg ruimte is.
Doe je dit altijd als je iets opslaat? Vragen of er genoeg ruimte is? (en zo ja, hoe doe je dat.. hoe weet je of er genoeg ruimte is?) Ik doe dat nu namelijk (nog) niet, maar ik throw dus gewoon een error als het misgaat.
Ik denk dat je 2 dingen kunt doen. Direct opslaan, en als het misgaat throw je een exception. Of je controleert altijd eerst of er genoeg ruimte is... en zo niet, dan gooi je alsnog een exception. Maar feitelijk is dat toch dubbelop? Omdat file_put_contents dat neem ik aan zelf ook al zal doen (kijken of er genoeg ruimte is)? En zo niet dan returnt ie false en kun je een exception gooien.
En dan weet je totaal niet wat er nou precies mis ging, alleen dat het mis ging...
Maar je hebt wel een punt. Een superstabiel systeem zal vanwege allerlei "checks and balances" (inclusief extra exceptions) nooit supersnel zijn.
Ik zou in dit geval dus niet of/of maar en/en doen. 5 kilobyte kun je naar alle waarschijnlijkheid wel silent kwijt, maar 50 MB gaan we toch eerst even controleren.
Ja, je hebt een punt. Maar hoe erg is dat? Als ik via mail een waarschuwing krijg "Filesystem could not save file blabla" dan weet ik toch genoeg? Dan is of de schijf vol, of de map-rechten kloppen niet. Of zie ik dat verkeerd?
>> Ik zou in dit geval dus niet of/of maar en/en doen. 5 kilobyte kun je naar alle waarschijnlijkheid wel silent kwijt, maar 50 MB gaan we toch eerst even controleren.
Oké, maar hoe weet ik hoeveel data het bestand dat moet worden opgeslagen in beslag gaat nemen? Dus stel we hebben data en die slaan we op:
$filesystem->save($data, $file);
Hoe weet je nu dan op voorhand hoe groot dat bestand gaat worden?