106 lines
26 KiB
HTML
106 lines
26 KiB
HTML
<!doctype html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>How to Build Your Infrastructure Monitoring in 2026 ·</title><meta name=description content="How to build your infrastructure monitoring in 2026, from SLI/SLO definition to actionable alerting"><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" rel=stylesheet><link rel=stylesheet href=https://omarghader.github.io/css/style.css><link rel=icon href=https://omarghader.github.io/favicon.ico><script type=application/ld+json>{"@context":"https://schema.org","@type":"BlogPosting","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/omarghader.github.io\/monitoring-infrastructure-guide-2026\/"},"articleSection":"blog","headline":"How to Build Your Infrastructure Monitoring in 2026","name":"How to Build Your Infrastructure Monitoring in 2026","description":"How to build your infrastructure monitoring in 2026, from SLI\/SLO definition to actionable alerting","inLanguage":"en-US","author":{"@type":"Person","name":"Omar Ghader"},"creator":{"@type":"Person","name":"Omar Ghader"},"publisher":{"@type":"Organization","name":"Omar Ghader","logo":{"@type":"ImageObject","url":"https:\/\/omarghader.github.io\/img/logo.png"}},"accountablePerson":{"@type":"Person","name":"Omar Ghader"},"copyrightHolder":{"@type":"Person","name":"Omar Ghader"},"copyrightYear":"2026","datePublished":"2026-08-05T00:00:00Z","dateModified":"2026-08-05T00:00:00Z","url":"https:\/\/omarghader.github.io\/monitoring-infrastructure-guide-2026\/","wordCount":"1565","image":"https:\/\/omarghader.github.io\/img/logo.png","keywords":["observability","monitoring","sre","opentelemetry","victoriametrics","loki","jaeger","vector",""]}</script></head><body><header class=site-header><div class=wrap><a href=https://omarghader.github.io/ class=site-brand>omar ghader<span>.</span></a><nav class=site-nav><a href=https://omarghader.github.io/work/>Work</a>
|
|
<a href=https://omarghader.github.io/blog/>Blog</a>
|
|
<a href=https://omarghader.github.io/about/>About</a>
|
|
<a href=https://omarghader.github.io/contact/>Contact</a></nav></div></header><main><article class=post-content><img class=post-hero-image src=https://omarghader.github.io/img/it_monitoring_guide_2026.png alt="How to Build Your Infrastructure Monitoring in 2026"><h1>How to Build Your Infrastructure Monitoring in 2026</h1><div class=meta>August 5, 2026
|
|
· observability, monitoring, sre, opentelemetry, victoriametrics, loki, jaeger, vector</div><p>Every year I get asked the same question by teams starting from scratch: “we have Grafana, we have some dashboards, why do we still get paged for things we didn’t see coming?” Most of the time, the answer isn’t a missing tool. It’s a missing method. Teams jump straight to “let’s install Prometheus” or “let’s buy a SaaS observability platform” before answering a much simpler question: what does “healthy” actually mean for this business?</p><p>I’ve built infrastructure monitoring from the ground up for several companies now, and I keep coming back to the same seven steps. This article is that playbook, the way I actually apply it in 2026.</p><h2 id=requirements>Requirements</h2><p>Before you touch any tool, you need:</p><ul><li>A clear list of the critical business flows your system supports (payments, checkouts, logins, API calls…)</li><li>Buy-in from the team on what “acceptable” looks like for those flows</li><li>A telemetry stack that can handle metrics, logs, and traces (I’ll give you mine below)</li></ul><blockquote><p><strong>If you get stuck at any point</strong>: reach out, I’m happy to help you think through your specific setup.</p></blockquote><h2 id=1-start-with-the-business-slislo-not-with-the-tool>1. Start with the business SLI/SLO, not with the tool</h2><p>This is the step almost everyone skips, and it’s the one that matters the most. Before deciding what to monitor, decide what “working” means for your business.</p><p>An <strong>SLI (Service Level Indicator)</strong> is a metric that reflects user-facing behavior. An <strong>SLO (Service Level Objective)</strong> is the target you set for that metric over a time window.</p><p>Example, if you work for a banking company:</p><ul><li><strong>SLI</strong>: the ratio of successful payment authorizations over total payment authorization attempts</li><li><strong>SLO</strong>: 99.95% of payment authorizations should succeed over a rolling 30-day window</li></ul><p>That single sentence changes everything downstream. It tells you:</p><ul><li>Which service is “tier 0” (payment authorization service)</li><li>What your error budget is (0.05% of failed authorizations per month)</li><li>What should page someone at 3am, and what can wait for Monday morning</li></ul><p>Do this exercise for every critical business flow before writing a single scrape config. If you skip it, you’ll end up monitoring infrastructure CPU graphs while your actual business metric silently burns through its error budget.</p><h2 id=2-know-what-to-monitor-then-pick-your-stack>2. Know what to monitor, then pick your stack</h2><p>Once your SLIs/SLOs are defined, list what you actually need visibility into to measure them:</p><ul><li><strong>Infrastructure</strong>: nodes, Kubernetes clusters, network, databases, message queues</li><li><strong>Applications</strong>: HTTP servers, HTTP clients, background jobs, gRPC services</li><li><strong>Business layer</strong>: the actual events tied to your SLI (a payment authorization call, a checkout event…)</li></ul><p>Only now do you pick the tech stack, because now you know what it needs to support. Here’s the generic stack I use on most projects:</p><table><thead><tr><th>Pillar</th><th>Tool</th><th>Role</th></tr></thead><tbody><tr><td>Metrics</td><td>VictoriaMetrics</td><td>Long-term, cost-efficient metrics storage (Prometheus-compatible)</td></tr><tr><td>Metrics agent</td><td>vmagent</td><td>Scraping and remote-writing metrics</td></tr><tr><td>Logs</td><td>Loki</td><td>Log aggregation, indexed by labels not full text</td></tr><tr><td>Logs & traces ingestion</td><td>OpenTelemetry Collector</td><td>Vendor-neutral receiver/processor/exporter pipeline</td></tr><tr><td>Traces</td><td>Jaeger</td><td>Distributed trace storage and visualization</td></tr></tbody></table><h3 id=the-three-pillars-and-what-each-is-actually-for>The three pillars, and what each is actually for</h3><p>It’s worth being explicit about this, because teams often use the wrong pillar to answer the wrong question:</p><ul><li><strong>Metrics</strong>: aggregated, cheap to store, great for trending and alerting. They answer “what” and “how much” (error rate is 2%, p99 latency is 800ms).</li><li><strong>Logs</strong>: high cardinality, detailed, expensive to store at full fidelity. They answer “why” during an investigation (this specific request failed because of X).</li><li><strong>Traces</strong>: the causal chain across services. They answer “where” in a distributed call the latency or error actually happened.</li></ul><p>None of the three replaces the others. Metrics tell you something is wrong, traces tell you where, logs tell you why.</p><h2 id=3-implement-and-scrape-favor-auto-instrumentation>3. Implement and scrape, favor auto-instrumentation</h2><p>Now you build the pipeline. My rule of thumb: instrument automatically first, add manual instrumentation only where auto-instrumentation doesn’t reach (custom business logic, internal queues, batch jobs).</p><p>Use the <a href=https://opentelemetry.io/docs/zero-code/>OpenTelemetry auto-instrumentation libraries</a> for your language. They hook into common frameworks (HTTP servers, HTTP clients, database drivers, gRPC) and emit metrics, traces, and sometimes logs without you writing a single line of instrumentation code.</p><p>Example, a Java service auto-instrumented and shipped straight to your collector, no code change required:</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash>java -javaagent:opentelemetry-javaagent.jar <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -Dotel.service.name<span style=color:#f92672>=</span>payment-authorization-service <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -Dotel.exporter.otlp.endpoint<span style=color:#f92672>=</span>http://otel-collector:4317 <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -Dotel.metrics.exporter<span style=color:#f92672>=</span>otlp <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -Dotel.traces.exporter<span style=color:#f92672>=</span>otlp <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -Dotel.logs.exporter<span style=color:#f92672>=</span>otlp <span style=color:#ae81ff>\
|
|
</span><span style=color:#ae81ff></span> -jar payment-service.jar
|
|
</code></pre></div><p>On the infrastructure side, vmagent scrapes your Prometheus-format endpoints (node_exporter, kube-state-metrics, cAdvisor, database exporters…):</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=color:#75715e># vmagent scrape config</span>
|
|
<span style=color:#f92672>scrape_configs</span>:
|
|
- <span style=color:#f92672>job_name</span>: <span style=color:#e6db74>'kubernetes-pods'</span>
|
|
<span style=color:#f92672>kubernetes_sd_configs</span>:
|
|
- <span style=color:#f92672>role</span>: <span style=color:#ae81ff>pod</span>
|
|
<span style=color:#f92672>relabel_configs</span>:
|
|
- <span style=color:#f92672>source_labels</span>: [<span style=color:#ae81ff>__meta_kubernetes_pod_annotation_prometheus_io_scrape]</span>
|
|
<span style=color:#f92672>action</span>: <span style=color:#ae81ff>keep</span>
|
|
<span style=color:#f92672>regex</span>: <span style=color:#66d9ef>true</span>
|
|
</code></pre></div><h2 id=4-build-a-telemetry-pipeline-that-correlates-not-just-collects>4. Build a telemetry pipeline that correlates, not just collects</h2><p>Collecting metrics, logs, and traces separately gets you three silos, not observability. The fix is enrichment: attach the same standard labels to everything, so you can pivot from a metric to a trace to a log for the exact same request.</p><p>I always enforce the <a href=https://opentelemetry.io/docs/specs/semconv/>OpenTelemetry semantic conventions</a> resource attributes as a baseline on every service:</p><ul><li><code>service.name</code>: the logical service, e.g. <code>payment-authorization-service</code></li><li><code>service.namespace</code>: the domain/team owning it, e.g. <code>payments</code></li><li><code>deployment.environment</code>: <code>production</code>, <code>staging</code>, etc.</li></ul><p>Enforce this at the OpenTelemetry Collector level so nothing gets ingested without it:</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=color:#75715e># otel-collector-config.yaml</span>
|
|
<span style=color:#f92672>processors</span>:
|
|
<span style=color:#f92672>resource</span>:
|
|
<span style=color:#f92672>attributes</span>:
|
|
- <span style=color:#f92672>key</span>: <span style=color:#ae81ff>service.namespace</span>
|
|
<span style=color:#f92672>value</span>: <span style=color:#ae81ff>payments</span>
|
|
<span style=color:#f92672>action</span>: <span style=color:#ae81ff>insert</span>
|
|
- <span style=color:#f92672>key</span>: <span style=color:#ae81ff>deployment.environment</span>
|
|
<span style=color:#f92672>value</span>: <span style=color:#ae81ff>production</span>
|
|
<span style=color:#f92672>action</span>: <span style=color:#ae81ff>insert</span>
|
|
|
|
<span style=color:#f92672>service</span>:
|
|
<span style=color:#f92672>pipelines</span>:
|
|
<span style=color:#f92672>traces</span>:
|
|
<span style=color:#f92672>receivers</span>: [<span style=color:#ae81ff>otlp]</span>
|
|
<span style=color:#f92672>processors</span>: [<span style=color:#ae81ff>resource, batch]</span>
|
|
<span style=color:#f92672>exporters</span>: [<span style=color:#ae81ff>otlp/jaeger]</span>
|
|
<span style=color:#f92672>metrics</span>:
|
|
<span style=color:#f92672>receivers</span>: [<span style=color:#ae81ff>otlp]</span>
|
|
<span style=color:#f92672>processors</span>: [<span style=color:#ae81ff>resource, batch]</span>
|
|
<span style=color:#f92672>exporters</span>: [<span style=color:#ae81ff>prometheusremotewrite/victoriametrics]</span>
|
|
<span style=color:#f92672>logs</span>:
|
|
<span style=color:#f92672>receivers</span>: [<span style=color:#ae81ff>otlp]</span>
|
|
<span style=color:#f92672>processors</span>: [<span style=color:#ae81ff>resource, batch]</span>
|
|
<span style=color:#f92672>exporters</span>: [<span style=color:#ae81ff>loki]</span>
|
|
</code></pre></div><p>With these three labels shared across your metrics, logs, and traces, you can go from “error rate spiked on <code>payment-authorization-service</code> in <code>production</code>” straight to the matching traces and logs, without guessing.</p><h2 id=5-build-a-red-dashboard-before-anything-fancier>5. Build a RED dashboard, before anything fancier</h2><p>Once telemetry is correlated by <code>service_name</code> and <code>service_namespace</code>, build one dashboard before all others: the <strong>RED dashboard</strong> (Rate, Errors, Duration).</p><p>Apply it to three things per service:</p><ul><li>HTTP server requests (inbound traffic)</li><li>HTTP client requests (outbound calls to dependencies)</li><li>Span metrics (generated from traces, gives you RED per operation, not just per HTTP route)</li></ul><p>Example PromQL for the “R” and “E” of a service, using OpenTelemetry’s standard <code>http.server.request.duration</code> metric:</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-promql data-lang=promql><span style=color:#75715e># Request rate</span>
|
|
<span style=color:#66d9ef>sum</span><span style=color:#f92672>(</span><span style=color:#66d9ef>rate</span><span style=color:#f92672>(</span>http_server_request_duration_seconds_count{service_namespace<span style=color:#f92672>=</span>"<span style=color:#e6db74>payments</span>"}[<span style=color:#e6db74>5m</span>]<span style=color:#f92672>))</span> <span style=color:#66d9ef>by</span> <span style=color:#f92672>(</span>service_name<span style=color:#f92672>)</span>
|
|
|
|
<span style=color:#75715e># Error rate (%)</span>
|
|
<span style=color:#66d9ef>sum</span><span style=color:#f92672>(</span><span style=color:#66d9ef>rate</span><span style=color:#f92672>(</span>http_server_request_duration_seconds_count{service_namespace<span style=color:#f92672>=</span>"<span style=color:#e6db74>payments</span>", http_response_status_code<span style=color:#f92672>=~</span>"<span style=color:#e6db74>5..</span>"}[<span style=color:#e6db74>5m</span>]<span style=color:#f92672>))</span> <span style=color:#66d9ef>by</span> <span style=color:#f92672>(</span>service_name<span style=color:#f92672>)</span>
|
|
<span style=color:#f92672>/</span>
|
|
<span style=color:#66d9ef>sum</span><span style=color:#f92672>(</span><span style=color:#66d9ef>rate</span><span style=color:#f92672>(</span>http_server_request_duration_seconds_count{service_namespace<span style=color:#f92672>=</span>"<span style=color:#e6db74>payments</span>"}[<span style=color:#e6db74>5m</span>]<span style=color:#f92672>))</span> <span style=color:#66d9ef>by</span> <span style=color:#f92672>(</span>service_name<span style=color:#f92672>)</span>
|
|
<span style=color:#f92672>*</span> <span style=color:#ae81ff>100</span>
|
|
|
|
<span style=color:#75715e># Duration (p99)</span>
|
|
<span style=color:#66d9ef>histogram_quantile</span><span style=color:#f92672>(</span><span style=color:#ae81ff>0.99</span>, <span style=color:#66d9ef>sum</span><span style=color:#f92672>(</span><span style=color:#66d9ef>rate</span><span style=color:#f92672>(</span>http_server_request_duration_seconds_bucket{service_namespace<span style=color:#f92672>=</span>"<span style=color:#e6db74>payments</span>"}[<span style=color:#e6db74>5m</span>]<span style=color:#f92672>))</span> <span style=color:#66d9ef>by</span> <span style=color:#f92672>(</span>service_name, le<span style=color:#f92672>))</span>
|
|
</code></pre></div><p>This one dashboard, applied consistently across every service, gives you a clear signal of application health over time before you’ve written a single custom panel. Everything else (business dashboards, infra dashboards, deep-dive panels) builds on top of it.</p><h2 id=6-alert-on-symptoms-not-noise-and-use-multi-window-burn-rate>6. Alert on symptoms, not noise, and use multi-window burn rate</h2><p>This is where most on-call setups fail. Two rules I don’t compromise on:</p><ul><li><strong>Alert on actionable conditions, not informative ones.</strong> “CPU is at 80%” is informative. “The payment SLO error budget will be exhausted in 2 hours at this burn rate” is actionable. If an alert doesn’t require a human to do something right now, it shouldn’t page anyone; it belongs on a dashboard.</li><li><strong>Use multi-window, multi-burn-rate alerting</strong> so you can tell a critical incident from something that can wait until tomorrow. This is straight out of the <a href=https://sre.google/workbook/alerting-on-slos/>Google SRE book’s alerting chapter</a>, and it’s the single highest-leverage thing you can implement for on-call sanity.</li></ul><p>The idea: page immediately only when the error budget is burning fast enough that waiting would breach the SLO. Use a short window to catch fast burns and a longer window to confirm it isn’t a blip, and use a slower threshold for tickets instead of pages.</p><p>Back to our banking example: SLO is 99.95% success over 30 days, meaning an error budget of 0.05%.</p><div class=highlight><pre style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=color:#75715e># VictoriaMetrics / Prometheus alerting rules</span>
|
|
<span style=color:#f92672>groups</span>:
|
|
- <span style=color:#f92672>name</span>: <span style=color:#ae81ff>payment-authorization-slo</span>
|
|
<span style=color:#f92672>rules</span>:
|
|
<span style=color:#75715e># Fast burn: page immediately.</span>
|
|
<span style=color:#75715e># Burning 14.4x the allowed rate would exhaust the 30-day budget in ~2 days.</span>
|
|
<span style=color:#75715e># Confirmed over both a 5m and 1h window to avoid paging on a blip.</span>
|
|
- <span style=color:#f92672>alert</span>: <span style=color:#ae81ff>PaymentAuthSLOFastBurn</span>
|
|
<span style=color:#f92672>expr</span>: |<span style=color:#e6db74>
|
|
</span><span style=color:#e6db74> (
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_failed_total{service_namespace="payments"}[5m]))
|
|
</span><span style=color:#e6db74> /
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_total{service_namespace="payments"}[5m]))
|
|
</span><span style=color:#e6db74> ) > (14.4 * 0.0005)
|
|
</span><span style=color:#e6db74> and
|
|
</span><span style=color:#e6db74> (
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_failed_total{service_namespace="payments"}[1h]))
|
|
</span><span style=color:#e6db74> /
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_total{service_namespace="payments"}[1h]))
|
|
</span><span style=color:#e6db74> ) > (14.4 * 0.0005)</span>
|
|
<span style=color:#f92672>labels</span>:
|
|
<span style=color:#f92672>severity</span>: <span style=color:#ae81ff>page</span>
|
|
<span style=color:#f92672>annotations</span>:
|
|
<span style=color:#f92672>summary</span>: <span style=color:#e6db74>"Payment authorization burning error budget fast, will breach SLO in ~2 days if it continues"</span>
|
|
|
|
<span style=color:#75715e># Slow burn: create a ticket, review during business hours.</span>
|
|
<span style=color:#75715e># Burning 3x the allowed rate would exhaust the budget in ~10 days.</span>
|
|
- <span style=color:#f92672>alert</span>: <span style=color:#ae81ff>PaymentAuthSLOSlowBurn</span>
|
|
<span style=color:#f92672>expr</span>: |<span style=color:#e6db74>
|
|
</span><span style=color:#e6db74> (
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_failed_total{service_namespace="payments"}[1h]))
|
|
</span><span style=color:#e6db74> /
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_total{service_namespace="payments"}[1h]))
|
|
</span><span style=color:#e6db74> ) > (3 * 0.0005)
|
|
</span><span style=color:#e6db74> and
|
|
</span><span style=color:#e6db74> (
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_failed_total{service_namespace="payments"}[6h]))
|
|
</span><span style=color:#e6db74> /
|
|
</span><span style=color:#e6db74> sum(rate(payment_authorization_total{service_namespace="payments"}[6h]))
|
|
</span><span style=color:#e6db74> ) > (3 * 0.0005)</span>
|
|
<span style=color:#f92672>labels</span>:
|
|
<span style=color:#f92672>severity</span>: <span style=color:#ae81ff>ticket</span>
|
|
<span style=color:#f92672>annotations</span>:
|
|
<span style=color:#f92672>summary</span>: <span style=color:#e6db74>"Payment authorization error budget burning steadily, investigate this week"</span>
|
|
</code></pre></div><p>What this buys you on-call:</p><ul><li>A fast, confirmed burn pages someone at 3am, because at that rate you’ll breach the monthly SLO within days.</li><li>A slow burn opens a ticket instead of paging, because at that rate you have days to weeks before the budget is exhausted.</li></ul><p>This is the difference between an on-call rotation that burns people out on noise, and one that pages only when it truly matters.</p><h2 id=7-you-now-have-the-method-not-just-the-tools>7. You now have the method, not just the tools</h2><p>At this point you have: business-defined SLIs/SLOs, a stack chosen because it fits what you need to monitor, auto-instrumented metrics/logs/traces, a correlated telemetry pipeline via standard labels, a RED dashboard per service, and burn-rate alerting that tells critical from “can wait.”</p><p>That’s not a finished monitoring setup, it’s a solid foundation you can build on: business dashboards, capacity planning, chaos testing against your SLOs, whatever comes next for your organization.</p><h2 id=conclusion>Conclusion</h2><p>If this was helpful, leave a comment and tell me how your monitoring setup looks today. If you’d like help implementing any of these steps for your specific stack, I’m happy to walk through it with you.</p><p>I wish you calm on-call shifts!</p><section class=cta-block><h2>Building something like this in production?</h2><p>I help teams turn setups like this into reliable, monitored infrastructure.</p><a href=https://omarghader.github.io/contact/ class="btn btn-primary">Get a free consulting call</a></section><div style=margin-top:16px><section style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:32px 28px;text-align:center"><p style="font-family:var(--font-display);font-size:19px;font-weight:600;margin:0 0 8px">Get my monitoring stack checklist</p><p style="color:var(--text-secondary);font-size:14px;margin:0 0 20px;max-width:420px;margin-left:auto;margin-right:auto">The exact checklist I use when setting up observability for a new team. No spam, unsubscribe anytime.</p><form class=newsletter-form action=https://omarghader.com/webhook/subscribe/newsletter/omarghader method=post style="display:flex;gap:8px;max-width:380px;margin:0 auto;flex-wrap:wrap"><input type=email name=email required placeholder=you@company.com style="flex:1;min-width:180px;padding:10px 12px;background:var(--surface-2);border:1px solid var(--border);border-radius:6px;color:var(--text);font-family:var(--font-body)">
|
|
<input type=hidden value=monitoring name=tag required>
|
|
<button type=submit class="btn btn-primary" style=border:none;flex-shrink:0>Send it to me</button></form><p class=newsletter-status style="margin:12px 0 0;font-size:13px"></p></section><script>(function(){document.querySelectorAll('.newsletter-form').forEach(function(a){a.addEventListener('submit',function(e){var b,c,d;e.preventDefault(),b=a.querySelector('button'),c=a.parentElement.querySelector('.newsletter-status'),d=b.textContent,b.disabled=!0,b.textContent='Sending...',c.textContent='',fetch(a.action,{method:'POST',body:new FormData(a),headers:{Accept:'application/json'}}).then(function(b){if(b.ok)a.reset(),c.style.color='var(--accent)',c.textContent="Check your inbox — it's on its way.";else throw new Error}).catch(function(){c.style.color='#e0714f',c.textContent="Something went wrong. Try again in a moment."}).finally(function(){b.disabled=!1,b.textContent=d})})})})()</script></div></article></main><footer class=site-footer><div class=wrap><span>© 2026 omar ghader</span>
|
|
<span><a href=https://github.com/omarghader target=_blank>github</a>
|
|
·
|
|
<a href=https://www.linkedin.com/in/marc-ghader/ target=_blank>linkedin</a></span></div></footer><script>window.addEventListener("load",function(){var b=window._mtm=window._mtm||[],a;b.push({'mtm.startTime':(new Date).getTime(),event:'mtm.Start'}),a=document.createElement('script'),a.async=!0,a.src="https://omarghader.com/matomo/js/container_JgixlzJl.js",document.head.appendChild(a)})</script><script>window.addEventListener("load",function(){var a=document.createElement("script");a.src="https://www.googletagmanager.com/gtag/js?id=G-SMB08YT14H",a.async=!0,document.head.appendChild(a),a.onload=function(){window.dataLayer=window.dataLayer||[];function a(){dataLayer.push(arguments)}a("js",new Date),a("config","G-SMB08YT14H")}})</script></body></html> |