From 50c8c153eaab46eb476b34e886296096aa9ae88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= <cuu508@gmail.com> Date: Thu, 23 Jan 2020 16:04:15 +0200 Subject: [PATCH] Documentation in Markdown. --- CHANGELOG.md | 1 + hc/front/management/commands/render_docs.py | 29 +++++ hc/front/urls.py | 1 + hc/front/views.py | 26 +++- static/css/channels.css | 2 +- static/css/docs.css | 21 +++- templates/base.html | 2 +- templates/docs/bash.html | 34 ++++++ templates/docs/bash.md | 39 ++++++ templates/docs/csharp.html | 7 ++ templates/docs/csharp.md | 10 ++ templates/docs/email.html | 12 ++ templates/docs/email.md | 16 +++ templates/docs/introduction.html | 26 ++++ templates/docs/introduction.md | 26 ++++ templates/docs/javascript.html | 13 ++ templates/docs/javascript.md | 17 +++ templates/docs/measuring_script_run_time.html | 30 +++++ templates/docs/measuring_script_run_time.md | 35 ++++++ templates/docs/monitoring_cron_jobs.html | 109 +++++++++++++++++ templates/docs/monitoring_cron_jobs.md | 113 ++++++++++++++++++ templates/docs/php.html | 4 + templates/docs/php.md | 7 ++ templates/docs/powershell.html | 23 ++++ templates/docs/powershell.md | 29 +++++ templates/docs/python.html | 32 +++++ templates/docs/python.md | 37 ++++++ templates/docs/ruby.html | 7 ++ templates/docs/ruby.md | 10 ++ templates/docs/signalling_failures.html | 24 ++++ templates/docs/signalling_failures.md | 28 +++++ templates/front/base_docs.html | 38 ++++-- templates/front/docs_cron.html | 4 +- templates/front/docs_nav_item.html | 3 + templates/front/docs_single.html | 22 ++++ 35 files changed, 817 insertions(+), 20 deletions(-) create mode 100644 hc/front/management/commands/render_docs.py create mode 100644 templates/docs/bash.html create mode 100644 templates/docs/bash.md create mode 100644 templates/docs/csharp.html create mode 100644 templates/docs/csharp.md create mode 100644 templates/docs/email.html create mode 100644 templates/docs/email.md create mode 100644 templates/docs/introduction.html create mode 100644 templates/docs/introduction.md create mode 100644 templates/docs/javascript.html create mode 100644 templates/docs/javascript.md create mode 100644 templates/docs/measuring_script_run_time.html create mode 100644 templates/docs/measuring_script_run_time.md create mode 100644 templates/docs/monitoring_cron_jobs.html create mode 100644 templates/docs/monitoring_cron_jobs.md create mode 100644 templates/docs/php.html create mode 100644 templates/docs/php.md create mode 100644 templates/docs/powershell.html create mode 100644 templates/docs/powershell.md create mode 100644 templates/docs/python.html create mode 100644 templates/docs/python.md create mode 100644 templates/docs/ruby.html create mode 100644 templates/docs/ruby.md create mode 100644 templates/docs/signalling_failures.html create mode 100644 templates/docs/signalling_failures.md create mode 100644 templates/front/docs_nav_item.html create mode 100644 templates/front/docs_single.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 545140f1..24db3641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Show sub-second durations with higher precision, 2 digits after decimal point (#321) - Replace the gear icon with three horizontal dots icon (#322) - Add a Pause button in the checks list (#312) +- Documentation in Markdown ### Bug Fixes - Increase the allowable length of Matrix room alias to 100 (#320) diff --git a/hc/front/management/commands/render_docs.py b/hc/front/management/commands/render_docs.py new file mode 100644 index 00000000..3415da87 --- /dev/null +++ b/hc/front/management/commands/render_docs.py @@ -0,0 +1,29 @@ +import os + +from django.conf import settings +from django.core.management.base import BaseCommand +import markdown + + +class Command(BaseCommand): + help = "Renders Markdown to HTML" + + def handle(self, *args, **options): + extensions = ["fenced_code", "codehilite", "tables"] + ec = {"codehilite": {"css_class": "highlight"}} + + docs_path = os.path.join(settings.BASE_DIR, "templates/docs") + for doc in os.listdir(docs_path): + if not doc.endswith(".md"): + continue + + print("Rendering %s" % doc) + + src_path = os.path.join(docs_path, doc) + dst_path = os.path.join(docs_path, doc[:-3] + ".html") + + text = open(src_path, "r", encoding="utf-8").read() + html = markdown.markdown(text, extensions=extensions, extension_configs=ec) + + with open(dst_path, "w", encoding="utf-8") as f: + f.write(html) diff --git a/hc/front/urls.py b/hc/front/urls.py index 68df1844..55142d5b 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -76,4 +76,5 @@ urlpatterns = [ path("docs/api/", views.docs_api, name="hc-docs-api"), path("docs/cron/", views.docs_cron, name="hc-docs-cron"), path("docs/resources/", views.docs_resources, name="hc-docs-resources"), + path("docs/<slug:doc>/", views.serve_doc, name="hc-serve-doc"), ] diff --git a/hc/front/views.py b/hc/front/views.py index 93474128..c34c53a8 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta as td import json +import os from urllib.parse import urlencode from croniter import croniter @@ -276,6 +277,28 @@ def docs(request): return render(request, "front/docs.html", ctx) +def serve_doc(request, doc): + path = os.path.join(settings.BASE_DIR, "templates/docs", doc + ".html") + if not os.path.exists(path): + raise Http404("not found") + + content = open(path, "r", encoding="utf-8").read() + content = content.replace("SITE_NAME", settings.SITE_NAME) + content = content.replace("PING_URL", settings.PING_ENDPOINT + "your-uuid-here") + content = content.replace( + "PING_EMAIL", "your-uuid-here@%s" % settings.PING_EMAIL_DOMAIN + ) + + ctx = { + "page": "docs", + "section": "home", + "section": doc, + "content": content, + } + + return render(request, "front/docs_single.html", ctx) + + def docs_api(request): ctx = { "page": "docs", @@ -290,8 +313,7 @@ def docs_api(request): def docs_cron(request): - ctx = {"page": "docs", "section": "cron"} - return render(request, "front/docs_cron.html", ctx) + return render(request, "front/docs_cron.html", {}) def docs_resources(request): diff --git a/static/css/channels.css b/static/css/channels.css index ad21fdfc..b4f2c3f9 100644 --- a/static/css/channels.css +++ b/static/css/channels.css @@ -192,7 +192,7 @@ table.channels-table > tbody > tr > th { float: left; font-size: 24px; width: 48px; - heigth: 48px; + height: 48px; background: #7ec1ea; border-radius: 32px; text-align: center; diff --git a/static/css/docs.css b/static/css/docs.css index f6b70a5c..0cbf9752 100644 --- a/static/css/docs.css +++ b/static/css/docs.css @@ -4,9 +4,18 @@ list-style: none; } -.docs-nav li a { +.docs-nav li { display: block; - padding-bottom: 10px; + margin-bottom: 10px; +} + +.docs-nav li.nav-header { + color: #333; + font-weight: bold; +} + +li + li.nav-header { + margin-top: 20px; } .docs-nav li.active > a { @@ -74,3 +83,11 @@ a.section:hover { border-radius: 4px; } +.docs-content li { + line-height: 2; +} + +.docs-content img { + max-width: 100%; + border: 6px solid #DDD; +} diff --git a/templates/base.html b/templates/base.html index 3cde5167..9c460703 100644 --- a/templates/base.html +++ b/templates/base.html @@ -124,7 +124,7 @@ {% endif %} <li {% if page == 'docs' %} class="active" {% endif %}> - <a href="{% url 'hc-docs' %}">Docs</a> + <a href="{% url 'hc-serve-doc' 'introduction' %}">Docs</a> </li> {% if request.user.is_authenticated %} diff --git a/templates/docs/bash.html b/templates/docs/bash.html new file mode 100644 index 00000000..9d2ddd97 --- /dev/null +++ b/templates/docs/bash.html @@ -0,0 +1,34 @@ +<h1>Shell scripts</h1> +<p>You can easily add SITE_NAME monitoring to a shell script. All you +have to do is make a HTTP request at the end of the script. curl and wget +are two common command line HTTP clients for that.</p> +<h2>Using curl</h2> +<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span> + +<span class="c1"># Exit immediately if any command exits with a non-zero status:</span> +<span class="nb">set</span> -e + +<span class="c1"># Do the work here</span> +<span class="nb">echo</span> <span class="s2">"Pretending to to make backups..."</span> +sleep <span class="m">5</span> +<span class="nb">echo</span> <span class="s2">"Backup complete!"</span> + +<span class="c1"># As the last thing, ping SITE_NAME using curl:</span> +<span class="hll">curl --retry <span class="m">3</span> PING_URL +</span></pre></div> + + +<h2>Using wget</h2> +<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span> + +<span class="c1"># Exit immediately if any command exits with a non-zero status:</span> +<span class="nb">set</span> -e + +<span class="c1"># Do the work here</span> +<span class="nb">echo</span> <span class="s2">"Pretending to to generate reports..."</span> +sleep <span class="m">5</span> +<span class="nb">echo</span> <span class="s2">"Report generation complete!"</span> + +<span class="c1"># As the last thing, ping SITE_NAME using wget:</span> +<span class="hll">wget PING_URL -O /dev/null +</span></pre></div> \ No newline at end of file diff --git a/templates/docs/bash.md b/templates/docs/bash.md new file mode 100644 index 00000000..05f9b660 --- /dev/null +++ b/templates/docs/bash.md @@ -0,0 +1,39 @@ +# Shell scripts + +You can easily add SITE_NAME monitoring to a shell script. All you +have to do is make a HTTP request at the end of the script. curl and wget +are two common command line HTTP clients for that. + +## Using curl + +```bash hl_lines="12" +#!/bin/sh + +# Exit immediately if any command exits with a non-zero status: +set -e + +# Do the work here +echo "Pretending to to make backups..." +sleep 5 +echo "Backup complete!" + +# As the last thing, ping SITE_NAME using curl: +curl --retry 3 PING_URL +``` + +## Using wget + +```bash hl_lines="12" +#!/bin/sh + +# Exit immediately if any command exits with a non-zero status: +set -e + +# Do the work here +echo "Pretending to to generate reports..." +sleep 5 +echo "Report generation complete!" + +# As the last thing, ping SITE_NAME using wget: +wget PING_URL -O /dev/null +``` \ No newline at end of file diff --git a/templates/docs/csharp.html b/templates/docs/csharp.html new file mode 100644 index 00000000..ebfb87c6 --- /dev/null +++ b/templates/docs/csharp.html @@ -0,0 +1,7 @@ +<h1>C</h1> +<p>Below is an example of making a HTTP request to SITE_NAME from C#.</p> +<div class="highlight"><pre><span></span><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Net</span><span class="p">.</span><span class="n">WebClient</span><span class="p">())</span> +<span class="p">{</span> + <span class="n">client</span><span class="p">.</span><span class="n">DownloadString</span><span class="p">(</span><span class="s">"PING_URL"</span><span class="p">);</span> +<span class="p">}</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/csharp.md b/templates/docs/csharp.md new file mode 100644 index 00000000..af60190e --- /dev/null +++ b/templates/docs/csharp.md @@ -0,0 +1,10 @@ +# C# + +Below is an example of making a HTTP request to SITE_NAME from C#. + +```csharp +using (var client = new System.Net.WebClient()) +{ + client.DownloadString("PING_URL"); +} +``` \ No newline at end of file diff --git a/templates/docs/email.html b/templates/docs/email.html new file mode 100644 index 00000000..1d1aab8b --- /dev/null +++ b/templates/docs/email.html @@ -0,0 +1,12 @@ +<h1>Email</h1> +<p>As an alternative to HTTP/HTTPS requests, you can "ping" checks by +sending an emails to special email addresses.</p> +<h2>Use Case: Email Delivery Monitoring</h2> +<p>Consider a cron job which runs weekly and sends weekly email +reports to a list of e-mail addresses. You have already set up a check to get alerted +when your cron job fails to run. But what you ultimately want to check is if +<strong>your emails are getting sent and delivered</strong>.</p> +<p>The solution: set up another check, and add its email address to your list of +recipient email addresses. Set its Period to 1 week. As long as your weekly email +script runs correctly, and there are no email delivery issues, +SITE_NAME will regularly receive an email, and the check and will stay up.</p> \ No newline at end of file diff --git a/templates/docs/email.md b/templates/docs/email.md new file mode 100644 index 00000000..352787c8 --- /dev/null +++ b/templates/docs/email.md @@ -0,0 +1,16 @@ +# Email + +As an alternative to HTTP/HTTPS requests, you can "ping" checks by +sending an emails to special email addresses. + +## Use Case: Email Delivery Monitoring + +Consider a cron job which runs weekly and sends weekly email +reports to a list of e-mail addresses. You have already set up a check to get alerted +when your cron job fails to run. But what you ultimately want to check is if +**your emails are getting sent and delivered**. + +The solution: set up another check, and add its email address to your list of +recipient email addresses. Set its Period to 1 week. As long as your weekly email +script runs correctly, and there are no email delivery issues, +SITE_NAME will regularly receive an email, and the check and will stay up. diff --git a/templates/docs/introduction.html b/templates/docs/introduction.html new file mode 100644 index 00000000..869f03f0 --- /dev/null +++ b/templates/docs/introduction.html @@ -0,0 +1,26 @@ +<h2>SITE_NAME</h2> +<p>SITE_NAME is a service for monitoring cron jobs and similar periodic processes:</p> +<ul> +<li>SITE_NAME <strong>listens for pings</strong> from services being monitored.</li> +<li>It <strong>keeps silent</strong> as long as pings arrive on time.</li> +<li>It <strong>raises an alert</strong> as soon as a ping does not arrive on time.</li> +</ul> +<p>SITE_NAME works as a <a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch">dead man's switch</a> for processes that need to +run continuously or on regular, known schedule:</p> +<ul> +<li>filesystem, database backups</li> +<li>task queues</li> +<li>database replication status</li> +<li>report generation scripts</li> +<li>periodic data import and sync jobs</li> +<li>periodic antivirus scans</li> +<li>DDNS updater scripts</li> +<li>SSL renewal scripts</li> +</ul> +<p>SITE_NAME is <em>not</em> the right tool for:</p> +<ul> +<li>monitoring website uptime by probing it with HTTP requests</li> +<li>collecting application performance metrics</li> +<li>error tracking</li> +<li>log aggregation</li> +</ul> \ No newline at end of file diff --git a/templates/docs/introduction.md b/templates/docs/introduction.md new file mode 100644 index 00000000..0b362572 --- /dev/null +++ b/templates/docs/introduction.md @@ -0,0 +1,26 @@ +## SITE_NAME + +SITE_NAME is a service for monitoring cron jobs and similar periodic processes: + +* SITE_NAME **listens for pings** from services being monitored. +* It **keeps silent** as long as pings arrive on time. +* It **raises an alert** as soon as a ping does not arrive on time. + +SITE_NAME works as a [dead man's switch](https://en.wikipedia.org/wiki/Dead_man%27s_switch) for processes that need to +run continuously or on regular, known schedule: + +* filesystem, database backups +* task queues +* database replication status +* report generation scripts +* periodic data import and sync jobs +* periodic antivirus scans +* DDNS updater scripts +* SSL renewal scripts + +SITE_NAME is *not* the right tool for: + +* monitoring website uptime by probing it with HTTP requests +* collecting application performance metrics +* error tracking +* log aggregation diff --git a/templates/docs/javascript.html b/templates/docs/javascript.html new file mode 100644 index 00000000..2dd7ee40 --- /dev/null +++ b/templates/docs/javascript.html @@ -0,0 +1,13 @@ +<h1>Javascript</h1> +<p>Below is an example of making a HTTP request to SITE_NAME from Node.js.</p> +<div class="highlight"><pre><span></span><span class="kd">var</span> <span class="nx">https</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'https'</span><span class="p">);</span> +<span class="nx">https</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">);</span> +</pre></div> + + +<p>You can also send pings from a browser environment. SITE_NAME sets the +<code>Access-Control-Allow-Origin:*</code> CORS header, so cross-domain AJAX requests work.</p> +<div class="highlight"><pre><span></span><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span> +<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'PING_URL'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span> +<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/javascript.md b/templates/docs/javascript.md new file mode 100644 index 00000000..28881533 --- /dev/null +++ b/templates/docs/javascript.md @@ -0,0 +1,17 @@ +# Javascript + +Below is an example of making a HTTP request to SITE_NAME from Node.js. + +```js +var https = require('https'); +https.get("PING_URL"); +``` + +You can also send pings from a browser environment. SITE_NAME sets the +`Access-Control-Allow-Origin:*` CORS header, so cross-domain AJAX requests work. + +```js +var xhr = new XMLHttpRequest(); +xhr.open('GET', 'PING_URL', true); +xhr.send(null); +``` diff --git a/templates/docs/measuring_script_run_time.html b/templates/docs/measuring_script_run_time.html new file mode 100644 index 00000000..1c77ecb7 --- /dev/null +++ b/templates/docs/measuring_script_run_time.html @@ -0,0 +1,30 @@ +<h1>Measuring Script Run Time</h1> +<p>Append <code>/start</code> to a ping URL and use it to signal when a job starts. + After receiving a start signal, Healthchecks.io will show the check as "Started". + It will store the "start" events and display the job execution times. The job + execution times are calculated as the time gaps between adjacent "start" and + "complete" events.</p> +<p>Signalling a start kicks off a separate timer: the job now <strong>must</strong> signal a +success within its configured "Grace Time", or it will get marked as "down".</p> +<p>Below is a code example in Python:</p> +<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">requests</span> +<span class="n">URL</span> <span class="o">=</span> <span class="s2">"PING_URL"</span> + + +<span class="c1"># "/start" kicks off a timer: if the job takes longer than</span> +<span class="c1"># the configured grace time, the check will be marked as "down"</span> +<span class="k">try</span><span class="p">:</span> + <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span> <span class="o">+</span> <span class="s2">"/start"</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> +<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span><span class="p">:</span> + <span class="c1"># If the network request fails for any reason, we don't want</span> + <span class="c1"># it to prevent the main job from running</span> + <span class="k">pass</span> + + +<span class="c1"># TODO: run the job here</span> +<span class="n">fib</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="k">if</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">2</span> <span class="k">else</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span> +<span class="k">print</span><span class="p">(</span><span class="s2">"F(42) = </span><span class="si">%d</span><span class="s2">"</span> <span class="o">%</span> <span class="n">fib</span><span class="p">(</span><span class="mi">42</span><span class="p">))</span> + +<span class="c1"># Signal success:</span> +<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span><span class="p">)</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/measuring_script_run_time.md b/templates/docs/measuring_script_run_time.md new file mode 100644 index 00000000..fb289bf0 --- /dev/null +++ b/templates/docs/measuring_script_run_time.md @@ -0,0 +1,35 @@ +# Measuring Script Run Time + + Append `/start` to a ping URL and use it to signal when a job starts. + After receiving a start signal, Healthchecks.io will show the check as "Started". + It will store the "start" events and display the job execution times. The job + execution times are calculated as the time gaps between adjacent "start" and + "complete" events. + +Signalling a start kicks off a separate timer: the job now **must** signal a +success within its configured "Grace Time", or it will get marked as "down". + +Below is a code example in Python: + +```python +import requests +URL = "PING_URL" + + +# "/start" kicks off a timer: if the job takes longer than +# the configured grace time, the check will be marked as "down" +try: + requests.get(URL + "/start", timeout=5) +except requests.exceptions.RequestException: + # If the network request fails for any reason, we don't want + # it to prevent the main job from running + pass + + +# TODO: run the job here +fib = lambda n: n if n < 2 else fib(n - 1) + fib(n - 2) +print("F(42) = %d" % fib(42)) + +# Signal success: +requests.get(URL) +``` diff --git a/templates/docs/monitoring_cron_jobs.html b/templates/docs/monitoring_cron_jobs.html new file mode 100644 index 00000000..eef612db --- /dev/null +++ b/templates/docs/monitoring_cron_jobs.html @@ -0,0 +1,109 @@ +<h2>Monitoring Cron Jobs</h2> +<p>SITE_NAME is perfectly suited for monitoring cron jobs. +Let's look at an example: a machine with the following cron job:</p> +<div class="highlight"><pre><span></span>$ crontab -l +<span class="c1"># m h dom mon dow command</span> + <span class="m">8</span> <span class="m">6</span> * * * /home/user/backup.sh +</pre></div> + + +<p>You can use SITE_NAME to get a notification whenever the <code>backup.sh</code> script does not +complete successfully. Here is how to set that up.</p> +<ol> +<li> +<p>If you have not already, sign up for a free SITE_NAME account.</p> +</li> +<li> +<p>In your SITE_NAME account, <strong>add a new check</strong>.</p> +<p>Note: in SITE_NAME, a <strong>check</strong> represents a single service you want to +monitor. For example, a single cron job. For each additional cron job you will +create another check. SITE_NAME pricing plans are structured primarily +around how many checks you can have in the account.</p> +</li> +<li> +<p>Give the check <strong>a meaningful name</strong>. Good naming will become +increasingly important as you add more checks to your account.</p> +</li> +<li> +<p>Edit the check's <strong>schedule</strong>:</p> +<ul> +<li>change its type from "Simple" to "Cron"</li> +<li>enter <code>8 6 * * *</code> in the cron epression field</li> +<li>set the timezone to match your machine's timezone</li> +</ul> +</li> +<li> +<p>Take note of your check's unique <strong>ping URL</strong></p> +</li> +</ol> +<p>Finally, edit your crontab and append a curl or wget call after the command:</p> +<div class="highlight"><pre><span></span>$ crontab -e +<span class="c1"># m h dom mon dow command</span> + <span class="m">8</span> <span class="m">6</span> * * * /home/user/backup.sh <span class="o">&&</span> curl -fsS --retry <span class="m">3</span> PING_URL > /dev/null +</pre></div> + + +<p>Now, each time your cron job runs, it will send a HTTP request to the ping URL.</p> +<p>Since SITE_NAME knows the schedule of your cron job, it can calculate +the dates and times when the job should run. As soon as your cron job doesn't +report at an expected time, SITE_NAME will send you a notification.</p> +<p>This monitoring technique takes care of various failure scenarios that could +potentially go unnoticed otherwise:</p> +<ul> +<li>The whole machine goes down (power outage, janitor stumbles on wires, VPS provider problems, etc.)</li> +<li>cron daemon is not running, or has invalid configuration</li> +<li>cron does start your task, but the task exits with non-zero exit code</li> +</ul> +<h2>Curl Options</h2> +<p>The extra options tells curl to not print anything to standard output unless +there is an error. Feel free to adjust the curl options to suit your needs.</p> +<table class="table curl-opts"> + <tr> + <th>&&</th> + <td>Run curl only if <code>/home/user/backup.sh</code> exits with an exit code 0</td> + </tr> + <tr> + <th> + -f, --fail + </th> + <td>Makes curl treat non-200 responses as errors</td> + </tr> + <tr> + <th>-s, --silent</th> + <td>Silent or quiet mode. Don't show progress meter or error messages.</td> + </tr> + <tr> + <th>-S, --show-error</th> + <td>When used with -s it makes curl show error message if it fails.</td> + </tr> + <tr> + <th>--retry <num></th> + <td> + If a transient error is returned when curl tries to perform a + transfer, it will retry this number of times before giving up. + Setting the number to 0 makes curl do no retries + (which is the default). Transient error means either: a timeout, + an FTP 4xx response code or an HTTP 5xx response code. + </td> + </tr> + <tr> + <th>> /dev/null</th> + <td> + Redirect curl's stdout to /dev/null (error messages go to stderr,) + </td> + </tr> +</table> + +<h2>Looking up Your Machine's Time Zone</h2> +<p>On modern GNU/Linux systems, you can look up the time zone using the +<code>timedatectl status</code> command and looking for "Time zone" in its output:</p> +<div class="highlight"><pre><span></span>$ timedatectl status + + Local time: C 2020-01-23 12:35:50 EET + Universal time: C 2020-01-23 10:35:50 UTC + RTC time: C 2020-01-23 10:35:50 +<span class="hll"> Time zone: Europe/Riga (EET, +0200) +</span>System clock synchronized: yes + NTP service: active + RTC in local TZ: no +</pre></div> \ No newline at end of file diff --git a/templates/docs/monitoring_cron_jobs.md b/templates/docs/monitoring_cron_jobs.md new file mode 100644 index 00000000..a286e378 --- /dev/null +++ b/templates/docs/monitoring_cron_jobs.md @@ -0,0 +1,113 @@ +## Monitoring Cron Jobs + +SITE_NAME is perfectly suited for monitoring cron jobs. +Let's look at an example: a machine with the following cron job: + +```bash +$ crontab -l +# m h dom mon dow command + 8 6 * * * /home/user/backup.sh +``` + +You can use SITE_NAME to get a notification whenever the `backup.sh` script does not +complete successfully. Here is how to set that up. + +1. If you have not already, sign up for a free SITE_NAME account. + +1. In your SITE_NAME account, **add a new check**. + + Note: in SITE_NAME, a **check** represents a single service you want to + monitor. For example, a single cron job. For each additional cron job you will + create another check. SITE_NAME pricing plans are structured primarily + around how many checks you can have in the account. + +1. Give the check **a meaningful name**. Good naming will become +increasingly important as you add more checks to your account. + +1. Edit the check's **schedule**: + + * change its type from "Simple" to "Cron" + * enter `8 6 * * *` in the cron epression field + * set the timezone to match your machine's timezone + +1. Take note of your check's unique **ping URL** + +Finally, edit your crontab and append a curl or wget call after the command: + +```bash +$ crontab -e +# m h dom mon dow command + 8 6 * * * /home/user/backup.sh && curl -fsS --retry 3 PING_URL > /dev/null +``` + +Now, each time your cron job runs, it will send a HTTP request to the ping URL. + +Since SITE_NAME knows the schedule of your cron job, it can calculate +the dates and times when the job should run. As soon as your cron job doesn't +report at an expected time, SITE_NAME will send you a notification. + +This monitoring technique takes care of various failure scenarios that could +potentially go unnoticed otherwise: + +* The whole machine goes down (power outage, janitor stumbles on wires, VPS provider problems, etc.) +* cron daemon is not running, or has invalid configuration +* cron does start your task, but the task exits with non-zero exit code + +## Curl Options + +The extra options tells curl to not print anything to standard output unless +there is an error. Feel free to adjust the curl options to suit your needs. + +<table class="table curl-opts"> + <tr> + <th>&&</th> + <td>Run curl only if <code>/home/user/backup.sh</code> exits with an exit code 0</td> + </tr> + <tr> + <th> + -f, --fail + </th> + <td>Makes curl treat non-200 responses as errors</td> + </tr> + <tr> + <th>-s, --silent</th> + <td>Silent or quiet mode. Don't show progress meter or error messages.</td> + </tr> + <tr> + <th>-S, --show-error</th> + <td>When used with -s it makes curl show error message if it fails.</td> + </tr> + <tr> + <th>--retry <num></th> + <td> + If a transient error is returned when curl tries to perform a + transfer, it will retry this number of times before giving up. + Setting the number to 0 makes curl do no retries + (which is the default). Transient error means either: a timeout, + an FTP 4xx response code or an HTTP 5xx response code. + </td> + </tr> + <tr> + <th>> /dev/null</th> + <td> + Redirect curl's stdout to /dev/null (error messages go to stderr,) + </td> + </tr> +</table> + +## Looking up Your Machine's Time Zone + +On modern GNU/Linux systems, you can look up the time zone using the +`timedatectl status` command and looking for "Time zone" in its output: + +```text hl_lines="6" +$ timedatectl status + + Local time: C 2020-01-23 12:35:50 EET + Universal time: C 2020-01-23 10:35:50 UTC + RTC time: C 2020-01-23 10:35:50 + Time zone: Europe/Riga (EET, +0200) +System clock synchronized: yes + NTP service: active + RTC in local TZ: no +``` \ No newline at end of file diff --git a/templates/docs/php.html b/templates/docs/php.html new file mode 100644 index 00000000..0901a278 --- /dev/null +++ b/templates/docs/php.html @@ -0,0 +1,4 @@ +<h1>PHP</h1> +<p>Below is an example of making a HTTP request to SITE_NAME from PHP.</p> +<div class="highlight"><pre><span></span><span class="x">file_get_contents('https://hc-ping.com/your-uuid-here');</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/php.md b/templates/docs/php.md new file mode 100644 index 00000000..be674a48 --- /dev/null +++ b/templates/docs/php.md @@ -0,0 +1,7 @@ +# PHP + +Below is an example of making a HTTP request to SITE_NAME from PHP. + +```php +file_get_contents('https://hc-ping.com/your-uuid-here'); +``` \ No newline at end of file diff --git a/templates/docs/powershell.html b/templates/docs/powershell.html new file mode 100644 index 00000000..f14dfe4b --- /dev/null +++ b/templates/docs/powershell.html @@ -0,0 +1,23 @@ +<h1>PowerShell</h1> +<p>You can use <a href="https://msdn.microsoft.com/en-us/powershell/mt173057.aspx">PowerShell</a> + and Windows Task Scheduler to automate various tasks on a Windows system. + From within a PowerShell script it is also easy to ping SITE_NAME.</p> +<p>Here is a simple PowerShell script that pings SITE_NAME. When scheduled to +run with Task Scheduler, it will essentially just send regular "I'm alive" messages. +You can of course extend it to do more things.</p> +<div class="highlight"><pre><span></span><span class="c1"># inside a PowerShell script:</span> +Invoke-RestMethod PING_URL +</pre></div> + + +<p>Save the above to e.g. <code>C:\Scripts\healthchecks.ps1</code>. +Then use the following command in a Scheduled Task to run the script:</p> +<div class="highlight"><pre><span></span>powershell.exe -ExecutionPolicy bypass -File C:<span class="se">\S</span>cripts<span class="se">\h</span>ealthchecks.ps1 +</pre></div> + + +<p>In simple cases, you can also pass the script to PowerShell directly, +using the "-command" argument:</p> +<div class="highlight"><pre><span></span><span class="c1"># Without an underlying script, passing the command to PowerShell directly:</span> +powershell.exe -command <span class="p">&</span><span class="o">{</span>Invoke-RestMethod PING_URL<span class="o">}</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/powershell.md b/templates/docs/powershell.md new file mode 100644 index 00000000..84d51ee8 --- /dev/null +++ b/templates/docs/powershell.md @@ -0,0 +1,29 @@ +# PowerShell + + You can use [PowerShell](https://msdn.microsoft.com/en-us/powershell/mt173057.aspx) + and Windows Task Scheduler to automate various tasks on a Windows system. + From within a PowerShell script it is also easy to ping SITE_NAME. + +Here is a simple PowerShell script that pings SITE_NAME. When scheduled to +run with Task Scheduler, it will essentially just send regular "I'm alive" messages. +You can of course extend it to do more things. + +```bash +# inside a PowerShell script: +Invoke-RestMethod PING_URL +``` + +Save the above to e.g. `C:\Scripts\healthchecks.ps1`. +Then use the following command in a Scheduled Task to run the script: + +```bash +powershell.exe -ExecutionPolicy bypass -File C:\Scripts\healthchecks.ps1 +``` + +In simple cases, you can also pass the script to PowerShell directly, +using the "-command" argument: + +```bash +# Without an underlying script, passing the command to PowerShell directly: +powershell.exe -command &{Invoke-RestMethod PING_URL} +``` diff --git a/templates/docs/python.html b/templates/docs/python.html new file mode 100644 index 00000000..7a490b98 --- /dev/null +++ b/templates/docs/python.html @@ -0,0 +1,32 @@ +<h1>Python</h1> +<p>If you are already using the requests library, it's convenient to also use it here:</p> +<div class="highlight"><pre><span></span><span class="c1"># using requests:</span> +<span class="kn">import</span> <span class="nn">requests</span> +<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">)</span> +</pre></div> + + +<p>Otherwise, you can use the urllib standard module.</p> +<div class="highlight"><pre><span></span><span class="c1"># urllib with python 3.x:</span> +<span class="kn">import</span> <span class="nn">urllib.request</span> +<span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">)</span> +</pre></div> + + +<div class="highlight"><pre><span></span><span class="c1"># urllib with python 2.x:</span> +<span class="kn">import</span> <span class="nn">urllib</span> +<span class="n">urllib</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">)</span> +</pre></div> + + +<p>You can include additional diagnostic information in the in the request body (for POST requests), or in the "User-Agent" request header:</p> +<div class="highlight"><pre><span></span><span class="c1"># Passing diagnostic information in the POST body:</span> +<span class="kn">import</span> <span class="nn">requests</span> +<span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="s2">"temperature=-7"</span><span class="p">)</span> +</pre></div> + + +<div class="highlight"><pre><span></span><span class="c1"># Passing diagnostic information in the User-Agent header:</span> +<span class="kn">import</span> <span class="nn">requests</span> +<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"PING_URL"</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">"User-Agent"</span><span class="p">:</span> <span class="s2">"temperature=-7"</span><span class="p">})</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/python.md b/templates/docs/python.md new file mode 100644 index 00000000..809643ca --- /dev/null +++ b/templates/docs/python.md @@ -0,0 +1,37 @@ +# Python + +If you are already using the requests library, it's convenient to also use it here: + +```python +# using requests: +import requests +requests.get("PING_URL") +``` + +Otherwise, you can use the urllib standard module. + +```python +# urllib with python 3.x: +import urllib.request +urllib.request.urlopen("PING_URL") +``` + +```python +# urllib with python 2.x: +import urllib +urllib.urlopen("PING_URL") +``` + +You can include additional diagnostic information in the in the request body (for POST requests), or in the "User-Agent" request header: + +```python +# Passing diagnostic information in the POST body: +import requests +requests.post("PING_URL", data="temperature=-7") +``` + +```python +# Passing diagnostic information in the User-Agent header: +import requests +requests.get("PING_URL", headers={"User-Agent": "temperature=-7"}) +``` diff --git a/templates/docs/ruby.html b/templates/docs/ruby.html new file mode 100644 index 00000000..e73e6b57 --- /dev/null +++ b/templates/docs/ruby.html @@ -0,0 +1,7 @@ +<h1>Ruby</h1> +<p>Below is an example of making a HTTP request to SITE_NAME from Ruby.</p> +<div class="highlight"><pre><span></span><span class="nb">require</span> <span class="s1">'net/http'</span> +<span class="nb">require</span> <span class="s1">'uri'</span> + +<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">'PING_URL'</span><span class="p">))</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/ruby.md b/templates/docs/ruby.md new file mode 100644 index 00000000..0b3e33f3 --- /dev/null +++ b/templates/docs/ruby.md @@ -0,0 +1,10 @@ +# Ruby + +Below is an example of making a HTTP request to SITE_NAME from Ruby. + +```ruby +require 'net/http' +require 'uri' + +Net::HTTP.get(URI.parse('PING_URL')) +``` \ No newline at end of file diff --git a/templates/docs/signalling_failures.html b/templates/docs/signalling_failures.html new file mode 100644 index 00000000..3d76b33c --- /dev/null +++ b/templates/docs/signalling_failures.html @@ -0,0 +1,24 @@ +<h1>Signalling failures</h1> +<p>Append <code>/fail</code> to a ping URL and use it to actively signal a failure. +Requesting the <code>/fail</code> URL will immediately mark the check as "down". +You can use this feature to minimize the delay from your monitored service failing +to you getting a notification.</p> +<p>Below is a skeleton code example in Python which signals a failure when the +work function returns an unexpected value or throws an exception:</p> +<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">requests</span> +<span class="n">URL</span> <span class="o">=</span> <span class="s2">"PING_URL"</span> + +<span class="k">def</span> <span class="nf">do_work</span><span class="p">():</span> + <span class="c1"># Do your number crunching, backup dumping, newsletter sending work here.</span> + <span class="c1"># Return a truthy value on success.</span> + <span class="c1"># Return a falsy value or throw an exception on failure.</span> + <span class="k">return</span> <span class="bp">True</span> + +<span class="n">success</span> <span class="o">=</span> <span class="bp">False</span> +<span class="k">try</span><span class="p">:</span> + <span class="n">success</span> <span class="o">=</span> <span class="n">do_work</span><span class="p">()</span> +<span class="k">finally</span><span class="p">:</span> + <span class="c1"># On success, requests PING_URL</span> + <span class="c1"># On failure, requests PING_URL/fail</span> + <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="n">URL</span> <span class="o">+</span> <span class="s2">"/fail"</span><span class="p">)</span> +</pre></div> \ No newline at end of file diff --git a/templates/docs/signalling_failures.md b/templates/docs/signalling_failures.md new file mode 100644 index 00000000..49fe04f9 --- /dev/null +++ b/templates/docs/signalling_failures.md @@ -0,0 +1,28 @@ +# Signalling failures + +Append `/fail` to a ping URL and use it to actively signal a failure. +Requesting the `/fail` URL will immediately mark the check as "down". +You can use this feature to minimize the delay from your monitored service failing +to you getting a notification. + +Below is a skeleton code example in Python which signals a failure when the +work function returns an unexpected value or throws an exception: + +```python +import requests +URL = "PING_URL" + +def do_work(): + # Do your number crunching, backup dumping, newsletter sending work here. + # Return a truthy value on success. + # Return a falsy value or throw an exception on failure. + return True + +success = False +try: + success = do_work() +finally: + # On success, requests PING_URL + # On failure, requests PING_URL/fail + requests.get(URL if success else URL + "/fail") +``` diff --git a/templates/front/base_docs.html b/templates/front/base_docs.html index 89c49a3a..f923ab0c 100644 --- a/templates/front/base_docs.html +++ b/templates/front/base_docs.html @@ -4,25 +4,39 @@ {% block content %} <div class="row"> - <div class="col-sm-2"> + <div class="col-sm-3"> <ul class="docs-nav"> - <li {% if section == "home" %} class="active" {% endif %}> - <a href="{% url 'hc-docs' %}">How {% site_name %} Works</a> + <li class="nav-header">Monitoring</li> + {% include "front/docs_nav_item.html" with slug="introduction" title="Introduction" %} + {% include "front/docs_nav_item.html" with slug="monitoring_cron_jobs" title="Monitoring cron jobs" %} + {% include "front/docs_nav_item.html" with slug="signalling_failures" title="Signalling failures" %} + {% include "front/docs_nav_item.html" with slug="measuring_script_run_time" title="Measuring script run time" %} + + <li class="nav-header">Platforms</li> + {% include "front/docs_nav_item.html" with slug="bash" title="Bash" %} + {% include "front/docs_nav_item.html" with slug="python" title="Python" %} + {% include "front/docs_nav_item.html" with slug="ruby" title="Ruby" %} + {% include "front/docs_nav_item.html" with slug="php" title="PHP" %} + {% include "front/docs_nav_item.html" with slug="csharp" title="C#" %} + {% include "front/docs_nav_item.html" with slug="javascript" title="Javascript" %} + {% include "front/docs_nav_item.html" with slug="powershell" title="PowerShell" %} + {% include "front/docs_nav_item.html" with slug="email" title="Email" %} + + <li class="nav-header">Developer Tools</li> + <li{% if section == "api" %} class="active"{% endif %}> + <a href="{% url 'hc-docs-api' %}">API</a> </li> - <li {% if section == "api" %} class="active" {% endif %}> - <a href="{% url 'hc-docs-api' %}">API Reference</a> - </li> - <li {% if section == "cron" %} class="active" {% endif %}> - <a href="{% url 'hc-docs-cron' %}">Cron Syntax</a> - </li> - <li {% if section == "resources" %} class="active" {% endif %}> - <a href="{% url 'hc-docs-resources' %}">Third-Party Resources</a> + <li {% if section == "resources" %} class="active"{% endif %}> + <a href="{% url 'hc-docs-resources' %}">Third-party resources</a> </li> + + <li class="nav-header">Cron</li> + <li><a href="{% url 'hc-docs-cron' %}">Cron Syntax Cheatsheet</a></li> </ul> </div> - <div class="col-sm-10"> + <div class="col-sm-9"> {% block docs_content %} {% endblock %} </div> diff --git a/templates/front/docs_cron.html b/templates/front/docs_cron.html index 47a667a1..43927a14 100644 --- a/templates/front/docs_cron.html +++ b/templates/front/docs_cron.html @@ -1,4 +1,4 @@ -{% extends "front/base_docs.html" %} +{% extends "base.html" %} {% load hc_extras %} {% block title %}Cron Syntax Cheatsheet - {% site_name %}{% endblock %} @@ -11,7 +11,7 @@ <meta name="keywords" content="cron syntax, crontab syntax, cron howto, cron tutorial, cron jobs, cron job example, monitor cron jobs"> {% endblock %} -{% block docs_content %} +{% block content %} <h2>Cron Syntax Cheatsheet</h2> diff --git a/templates/front/docs_nav_item.html b/templates/front/docs_nav_item.html new file mode 100644 index 00000000..4613cd12 --- /dev/null +++ b/templates/front/docs_nav_item.html @@ -0,0 +1,3 @@ +<li{% if slug == section %} class="active"{% endif %}> + <a href="{% url 'hc-serve-doc' slug %}">{{ title }}</a> +</li> \ No newline at end of file diff --git a/templates/front/docs_single.html b/templates/front/docs_single.html new file mode 100644 index 00000000..7e2a3263 --- /dev/null +++ b/templates/front/docs_single.html @@ -0,0 +1,22 @@ +{% extends "front/base_docs.html" %} +{% load compress static hc_extras %} + +{% block title %}Documentation - {% site_name %}{% endblock %} +{% block description %} + <meta name="description" content="Monitor any service that can make a HTTP request or send an email: cron jobs, Bash scripts, Python, Ruby, Node, PHP, JS, ..."> +{% endblock %} +{% block keywords %} + <meta name="keywords" content="healthchecks, crontab monitoring, python health check, bash health check, cron monitoring, cron tutorial, cron howto, api health check, open source"> +{% endblock %} + + +{% block docs_content %}<div class="docs-content">{{ content|safe }}</div>{% endblock %} + +{% block scripts %} +{% compress js %} +<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script> +<script src="{% static 'js/bootstrap.min.js' %}"></script> +<script src="{% static 'js/clipboard.min.js' %}"></script> +<script src="{% static 'js/snippet-copy.js' %}"></script> +{% endcompress %} +{% endblock %}