{"id":195,"date":"2009-11-12T16:00:02","date_gmt":"2009-11-13T00:00:02","guid":{"rendered":"http:\/\/35.225.155.113\/blog\/index.php\/2009\/11\/12\/a_stepped_up_remote_apache_monitor_in_perl\/"},"modified":"2019-10-13T12:56:15","modified_gmt":"2019-10-13T19:56:15","slug":"a-stepped-up-remote-apache-monitor-in-perl","status":"publish","type":"post","link":"https:\/\/www.weinstein.org\/blog\/index.php\/2009\/11\/a-stepped-up-remote-apache-monitor-in-perl.html","title":{"rendered":"A Stepped Up Remote Apache Monitor in Perl"},"content":{"rendered":"<p style=\"margin-bottom: 0in;\">Back in September I outline a <a href=\"http:\/\/pdw.weinstein.org\/2009\/09\/simple-remote-monitor-for-apache-with-perl.html\">simple<br \/>\nPerl script to remotely monitor the status of various web servers<\/a> I manage and report on any failures. One shortcoming of the script is that<br \/>\nit has no memory of the previous state of the websites listed for<br \/>\npolling. Thus, once a site fails, the script will continuously report<br \/>\non the failure until resolved.<\/p>\n<p>For some, this might be just fine, a<br \/>\nsimple repetitive reminder until corrected. For<br \/>\nothers however, this might not be ideal. If, for example, the problem<br \/>\nis non-trivial to solve, the last thing one needs is a nagging every<br \/>\nfew minutes that the issue has yet to be resolved.<\/p>\n<p>I for one am all for notification<br \/>\nwithout excessive nagging.<\/p>\n<p>The obvious answer to this dilemma is<br \/>\nto store the previous state of the server such that it can be used to<br \/>\ntest against the currently state; if the state of the server has<br \/>\nchanged, a notification gets sent. Thus one straightforward notification that something has changed.<\/p>\n<p>As a bonus, by reporting on the change<br \/>\nof state, the script will now report on when the server has come back<br \/>\nonline as well as when it has failed. This simple change eliminates<br \/>\nwhat would have been a manual process previously; notifying<br \/>\nstakeholders that the issue has been resolved.<\/p>\n<p>Since the Perl script is evoked by <a property=\"ctag:label\" resource=\"http:\/\/rdf.freebase.com\/ns\/guid\/9202a8c04000641f80000000045c9c5b\" typeof=\"ctag:Tag\" xmlns:ctag=\"http:\/\/commontag.org\/ns#\" class=\"zem_slink rdfa\" href=\"http:\/\/en.wikipedia.org\/wiki\/Cron\" title=\"Cron\" rel=\"ctag:means wikipedia\">cron<\/a><br \/>\non a regular basis and terminates once polling is complete, the<br \/>\n&#8220;current&#8221; state of a site will need to be store in secondary<br \/>\nmemory, i.e. on disk, for future comparison. This is pretty<br \/>\nstraightforward in Perl:<\/p>\n<div style=\"padding: 5px; background-color: rgb(204, 204, 204);\">\n<pre>sub logState ($$) {\nmy ( $host, $state, $time ) = @_;\n# Create a filehandle on our log file\nmy $fh = FileHandle-&gt;new(\"&gt;&gt; $fileLoc\");\nif (defined $fh) {\n# Print to file the necessary information\n# delimited with a colon\nprint $fh \"$host:$state:\" .$time-&gt;datetime. \"\\n\";\n$fh-&gt;close;\n}\n}\n<\/pre>\n<\/div>\n<p style=\"margin-bottom: 0in;\">With a new Filehandle object the script<br \/>\nopens the file previously assigned to the $fileLoc variable for<br \/>\nappending (the &#8216;&gt;&gt;&#8217; immediately prior to the variable denotes<br \/>\nwrite by appending).<\/p>\n<p>If a Filehandle object has been<br \/>\nsuccessfully created, the next step is to write a line to the file<br \/>\nwith the information necessary for the next iteration of the monitor<br \/>\nscript, specifically the host information and its current state.<\/p>\n<p>Note that each line (\\n) in the file<br \/>\nwill denote information about a specific site and that the related<br \/>\ninformation is separated by a colon (:). This will be pertinent later in the code, reading of the log file at the next scheduled<br \/>\nexecution of the monitor script:<\/p>\n<div style=\"padding: 5px; background-color: rgb(204, 204, 204);\">\n<pre># Our array of polling sites' previous state\nmy @hostStates = ();\n# Populate said array with information from log file\nmy $fh = FileHandle-&gt;new(\"&lt; $fileLoc\");\nwhile ( &lt;$fh&gt; ) {\nmy( $line ) = $_;\nchomp( $line );\npush ( @hostStates, $line );\n}\n$fh-&gt;close;\n<\/pre>\n<\/div>\n<p style=\"margin-bottom: 0in;\">In this bit of code the goal is to get<br \/>\nthe previously logged state of each site and populate an array with<br \/>\nthe information. At the moment how each record is delimited isn&#8217;t of<br \/>\nconcern, but simply that each line is information relating to a<br \/>\nspecific site and gets its own node in the array.<\/p>\n<p>Note, since the objective here is to simply read the log file the &#8220;&lt;&#8221; is used by the filehandle to denote that the file is &#8220;read-only&#8221; and not &#8220;append&#8221;.<\/p>\n<p>Once the polling of a specific site<br \/>\noccurs, the first item of concern is determining the site&#8217;s previous state. For<br \/>\nthat the following bit of code is put to use:<\/p>\n<div style=\"padding: 5px; background-color: rgb(204, 204, 204);\">\n<pre>sub getPreviousState ($) {\nmy ( $host ) = @_;\n# For each node in the array do the following\nforeach ( @hostStates ) {\nmy( $line ) = $_;\n# Break up the information\n# using our delimiter, the colon\nmy ($domain, $state, $time) = split(\/:\/, $line, 3);\n# If we find our site return the previous state\nif ( $domain eq $host ) {\nreturn $state;\n}\n}\n}\n<\/pre>\n<\/div>\n<p style=\"margin-bottom: 0in;\">In this function each element in the<br \/>\narray is broken down to relevant information using the <a href=\"http:\/\/perldoc.perl.org\/functions\/split.html\">split<br \/>\nfunction<\/a>, which <a property=\"ctag:label\" resource=\"http:\/\/rdf.freebase.com\/ns\/guid\/9202a8c04000641f80000000002b3dff\" typeof=\"ctag:Tag\" xmlns:ctag=\"http:\/\/commontag.org\/ns#\" class=\"zem_slink rdfa\" href=\"http:\/\/en.wikipedia.org\/wiki\/Delimiter\" title=\"Delimiter\" rel=\"ctag:means wikipedia\">delimits<\/a> the record by a given character, the colon.<br \/>\nFrom here it is a simple matter of testing the two states, the<br \/>\nprevious and current state before rolling into the notification<br \/>\nprocess.<\/p>\n<p>The complete, improved remote monitor:<\/p>\n<div style=\"padding: 5px; background-color: rgb(204, 204, 204);\">\n<pre>#!\/usr\/bin\/perl\nuse strict;\nuse FileHandle;\nuse Time::Piece;\nuse LWP::UserAgent;\nuse Net::Ping;\nuse Net::Twitter::Lite;\n### Start Global Settings ###\nmy $fileLoc = \"\/var\/log\/state.txt\";\nmy @hosts = ( \"pdw.weinstein.org\", \"www.weinstein.org\" );\n### End Global Settings ###\n# Our array of polling sites' previous state\nmy @hostStates = ();\n# Populate said array with information from log file\nmy $fh = FileHandle-&gt;new(\"&lt; $fileLoc\");\nwhile ( &lt;$fh&gt; ) {\nmy( $line ) = $_;\nchomp( $line );\npush ( @hostStates, $line );\n}\n$fh-&gt;close;\n# Clear out the file by writing anew\nmy $fh = FileHandle-&gt;new(\"&gt; $fileLoc\");\n$fh-&gt;close;\nforeach my $host ( @hosts ) {\nmy $previousState = getPreviousState( $host );\nmy $url = \"http:\/\/$host\";\nmy $ua = LWP::UserAgent-&gt;new;\nmy $response = $ua-&gt;get( $url );\nmy $currentState = $response-&gt;code;\nmy $time = localtime;\n# If states are not equal we need to notify someone\nif ( $previousState ne $currentState ) {\n# Do we have an status code?\nif ( $response-&gt;code ) {\nreportError(\"$host reports\n$response-&gt;message.\\n\");\n} else {\n# HTTP is not responding,\n# is it the network connection down?\nmy $p = Net::Ping-&gt;new(\"icmp\");\nif ( $p-&gt;ping( $host, 2 )) {\nreportError ( \"$host is responding,\nbut Apache is not.\\n\" );\n} else {\nreportError ( \"$host is unreachable.\\n\" );\n}\n}\n# Not done yet, we need to log\n# the current state for future use\nlogState( $host, $currentState, $time )\n}\nsub reportError ($) {\nmy ( $msg ) = @_;\nmy $nt = Net::Twitter::Lite-&gt;new(\nusername =&gt; $username,\npassword =&gt; $pasword );\nmy $result = eval { $nt-&gt;update( $msg ) };\nif ( !$result ) {\n# Twitter has failed us,\n# need to get the word out still...\nsmsEmail ( $msg );\n}\n}\nsub smsEmail ($) {\nmy ( $msg ) = @_;\nmy $to = \"7735551234\\@txt.exaple.org\";\nmy $from = \"pdw\\@weinstein.org\";\nmy $subject = \"Service Notification\";\nmy $sendmail = '\/usr\/lib\/sendmail';\nopen(MAIL, \"|$sendmail -oi -t\");\nprint MAIL \"From: $from\\n\";\nprint MAIL \"To: $to\\n\";\nprint MAIL \"Subject: $subject\\n\\n\";\nprint MAIL $msg;\nclose( MAIL );\n}\nsub logState ($$) {\nmy ( $host, $state, $time ) = @_;\n# Create a filehandle on our log file\nmy $fh = FileHandle-&gt;new(\"&gt;&gt; $fileLoc\");\nif (defined $fh) {\n# Print to file the necessary information,\n# delimited with a colon\nprint $fh \"$host:$state:\" .$time-&gt;datetime. \"\\n\";\n$fh-&gt;close;\n}\n}\nsub getPreviousState ($) {\nmy ( $host ) = @_;\n# For each node in the array do the following\nforeach ( @hostStates ) {\nmy( $line ) = $_;\n# Break up the information using our delimiter,\n# the colon\nmy ($domain, $state, $time) = split(\/:\/, $line, 3);\n# If we find our site return the previous state\nif ( $domain eq $host ) {\nreturn $state;\n}\n}\n}\n<\/pre>\n<\/div>\n<div style=\"margin-top: 10px; height: 15px;\" class=\"zemanta-pixie\"><img decoding=\"async\" style=\"border: medium none ; float: right;\" class=\"zemanta-pixie-img\" alt=\"\" src=\"http:\/\/img.zemanta.com\/pixy.gif?x-id=c82158b1-33fd-405f-bb50-e37d5a2489db\"><span class=\"zem-script more-related pretty-attribution\"><script type=\"text\/javascript\" src=\"http:\/\/static.zemanta.com\/readside\/loader.js\" defer=\"defer\"><\/script><\/span><\/div>\n","protected":false},"excerpt":{"rendered":"<p>A Simple Remote Monitor for Apache, Enhanced.<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,92,82],"tags":[244,44,43,243],"_links":{"self":[{"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/posts\/195"}],"collection":[{"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=195"}],"version-history":[{"count":2,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/posts\/195\/revisions"}],"predecessor-version":[{"id":714,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/posts\/195\/revisions\/714"}],"wp:attachment":[{"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=195"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=195"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.weinstein.org\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=195"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}