<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris</title>
    <description>The latest articles on DEV Community by Chris (@netdsg).</description>
    <link>https://dev.to/netdsg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4196072%2F72dd4a02-ff6b-4cac-a9f2-17a121093cd8.jpeg</url>
      <title>DEV Community: Chris</title>
      <link>https://dev.to/netdsg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/netdsg"/>
    <language>en</language>
    <item>
      <title>Per Service CPU Usage Monitoring - A Job For Prometheus &amp; Grafana</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Wed, 11 Nov 2026 04:31:47 +0000</pubDate>
      <link>https://dev.to/netdsg/per-service-cpu-usage-monitoring-a-job-for-prometheus-grafana-3nc7</link>
      <guid>https://dev.to/netdsg/per-service-cpu-usage-monitoring-a-job-for-prometheus-grafana-3nc7</guid>
      <description>&lt;p&gt;Recently we had a situation where it would have been good to have had per service CPU usage graphed over time.&lt;/p&gt;

&lt;p&gt;Some of our servers are run in a 'multi tenant' mode where we deliver a service to 20 different customers, with each customer using their own dedicated systemd service.  Some of these customers use the service quite aggressively while others are well behaved.&lt;/p&gt;

&lt;p&gt;Recently we restarted one of the services of an 'aggressive' customer in order to realize a new configuration and noticed that the overall CPU usage on the server dropped in half.  This observation spawned a lot of questions...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was this one service accounting for 50% of the overall CPU usage on the server?&lt;/li&gt;
&lt;li&gt;Was the new configuration more efficient in some way?&lt;/li&gt;
&lt;li&gt;Could it be coincidence that this service restart resulted in less CPU consumption?&lt;/li&gt;
&lt;li&gt;Did the CPU usage for this particular service increase over time, or had it always been like this?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server had a lot of CPU headroom, there was no immediate danger, yet it felt like there was a tiger in the bushes waiting for us. Suddenly we felt the need to increase our situational awareness.&lt;/p&gt;

&lt;p&gt;Our monitoring server graphed total CPU usage, but had no notion of 'per process CPU usage'. If we had a way to visualize each customers CPU utilization it could help answer a lot of questions.&lt;/p&gt;

&lt;p&gt;So! How to get this situational awareness?  After discussing with our staff, searching the internet, and posing the question to our AI agents, a solution using Prometheus for data collection and Grafana for visualization was selected.&lt;/p&gt;

&lt;h3&gt;
  
  
  10,000 Foot View
&lt;/h3&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgsjrdr0cdt6svkcsu2bx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgsjrdr0cdt6svkcsu2bx.png" alt=" " width="684" height="325"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A prometheus agent runs on the customer facing server that exposes per process related statistics &lt;/li&gt;
&lt;li&gt;Prometheus collector runs on the monitoring server, polls the customer facing server and updates a data store&lt;/li&gt;
&lt;li&gt;Grafana queries the prometheus datastore and renders time series graphs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Multi Tenant Server Provisioning
&lt;/h2&gt;

&lt;p&gt;First we installed &lt;code&gt;prometheus-process-exporter&lt;/code&gt; on the multi-tenant server.  By default the &lt;code&gt;prometheus-process-exporter&lt;/code&gt; service has no configuration.  The only configuration related file installed by default is &lt;code&gt;/etc/default/prometheus-process-exporter&lt;/code&gt; and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set the command-line arguments to pass to the server.&lt;/span&gt;
&lt;span class="c"&gt;# Due to shell escaping, to pass backslashes for regexes, you need to double&lt;/span&gt;
&lt;span class="c"&gt;# them (\\d for \d). If running under systemd, you need to double them again&lt;/span&gt;
&lt;span class="c"&gt;# (\\\\d to mean \d), and escape newlines too.&lt;/span&gt;
&lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to update this file to listen on a TCP socket and point it to a configuration file.  The configuration file uses the yaml format.  Here is an example we placed in &lt;code&gt;/etc/prometheus/process-exporter.yml&lt;/code&gt; (the &lt;code&gt;/etc/prometheus&lt;/code&gt; directory may need to be created):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;process_names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"{{.Cgroups}}"&lt;/span&gt;
    &lt;span class="na"&gt;comm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="s"&gt;rust-widget&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case every customer runs the same binary for their service, the binary name being rust-widget.  The Cgroups argument will name each group after the seen service name, each systemd service gets its own group and children forked by it are associated with it.&lt;/p&gt;

&lt;p&gt;Update the ARGS declaration in &lt;code&gt;/etc/default/prometheus-process-exporter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--config.path=/etc/prometheus/process-exporter.yml --web.listen-address=10.10.10.10:9256"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart the &lt;code&gt;prometheus-process-exporter&lt;/code&gt; service to realize the change&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Server Provisioning
&lt;/h2&gt;

&lt;p&gt;Next we need to configure the monitoring server to poll this new prometheus endpoint.  To do this we'll add a 'job' to &lt;code&gt;/etc/prometheus/prometheus.yml&lt;/code&gt;. This job goes in the &lt;code&gt;scrape_configs&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;multi-tenant-1&lt;/span&gt;
    &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&lt;/span&gt;
    &lt;span class="na"&gt;scrape_timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'10.10.10.10:9256'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;metric_relabel_configs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;groupname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'.*/([^/]+rust-widget)\.service.*'&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service&lt;/span&gt;
        &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'$1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;metric_relabel_configs&lt;/code&gt; section is worth talking about.  In our case all our customer facing services have a naming convention of &lt;code&gt;&amp;lt;customer&amp;gt;_rust-widget&lt;/code&gt;.  Since we are only interested in CPU usage information of these services we use a regex that matches that.&lt;/p&gt;

&lt;p&gt;After changing this file restart &lt;code&gt;prometheus&lt;/code&gt; to realize it.  We can verify the state of the new endpoint by having a look at the prometheus web server.  the default web server port for &lt;code&gt;prometheus&lt;/code&gt; is 9090.  The path that provides endpoint status is:  &lt;code&gt;http://127.0.0.1:9090/classic/targets&lt;/code&gt;.  If prometheus is able to query the new endpoint and things are working well the endpoint State will be 'UP'.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now, how to visualize this?&lt;/strong&gt; Either create or edit an existing dashboard, then add a time series panel.  Select the prometheus data source (we made the assumption that prometheus is already configured as a data source for the grafana instance).  We used Claude to recommend a ProMQ query as most humans have little interest in learning this query language.  Claude returned this for a per job graph and it worked as desired strait away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="na"&gt;sum by (service) (rate(namedprocess_namegroup_cpu_seconds_total{job&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"multi-tenant-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=~&lt;/span&gt;&lt;span class="s2"&gt;".+"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The resulting graph looks like this:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwo75onkpocwj8co212i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwo75onkpocwj8co212i.jpg" alt=" " width="441" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next time we observe some odd change in CPU consumption we will be better positioned to know why.  Reducing the tigers in the bushes one at a time...&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>prometheus</category>
    </item>
    <item>
      <title>Zero Day Protection - NGINX web server example</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 05 Nov 2026 10:49:57 +0000</pubDate>
      <link>https://dev.to/netdsg/zero-day-protection-nginx-web-server-example-3087</link>
      <guid>https://dev.to/netdsg/zero-day-protection-nginx-web-server-example-3087</guid>
      <description>&lt;p&gt;The recent PHP vulnerability got me thinking about how to prevent any kind of HTTP exploit be it known or not.  In order for an attacker to exploit a weakness they must discover it. In order to discover an exploitable service the attacker much send a lot of probes.  we see these probes on any web server that's open to the internet 24/7.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The web server at my home is constantly, shamelessly groped.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This inspired me to write a script that tails the web server log and takes action based on configurable attributes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If we see three requests resulting in HTML status codes that have been designated 'not normal' for this web server, we signal Netfilter to block any traffic from the source IP of the request.&lt;/li&gt;
&lt;li&gt;If we see any request from a HTTP user agent we've deemed unworthy we signal Netfilter to block any traffic from the source IP of the request.  &lt;strong&gt;Seriously, would any human be using a user agent such as?&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36; compatible; OAI-SearchBot/1.4; robots.txt; +https://openai.com/searchbot"&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dropping the traffic at layer 3 appeals to me&lt;/strong&gt; as we prevent any other buffoonery from this IP address.  If we send and ICMP unreachable back, this consumes our resources.  By dropping it the attacker knows not if it's just a slow connection or a non-existent server and will wait till it times out, consuming their resources. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The script goes like this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tail the log&lt;/li&gt;
&lt;li&gt;Use a 'while' loop to process each new log message&lt;/li&gt;
&lt;li&gt;The NGINX log was converted to a JSON format in order to have a predictable data structure.  This is very helpful for scripts that ingest it.  We realize the benefit of the data structure by loading log message with the python3 json module&lt;/li&gt;
&lt;li&gt;Next we evaluate the log data:

&lt;ul&gt;
&lt;li&gt;if the request was from a trusted network stop processing it, we are OK with any request from a trusted network.&lt;/li&gt;
&lt;li&gt;update a hash of clients that keeps track of each public IP address, status codes, and number of times the status code was returned.&lt;/li&gt;
&lt;li&gt;If the user agent contains a predefined term that's deemed unsavory signal Netfilter to block the public IP address of the request&lt;/li&gt;
&lt;li&gt;if the returned status codes is in a predefined list and exceeds a predefined number of occurrences block the public IP address of the request.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This routine looks like this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logTail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logLine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clientIp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;remote_addr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;checkAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trust&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;untrust&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;untrusted&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clientIp&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http_user_agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;logHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http_user_agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stupid user agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;doBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unwantedAgents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http_user_agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blocking unwanted user agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;doBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blockableStatusCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;blockThreshold&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;clientIp&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blockedSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;strike &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;doBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;strike &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;clientIp&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The complete script may be found here:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://gist.github.com/netdsg/cc338230a46f8a01c1b25983d5935143" rel="noopener noreferrer"&gt;https://gist.github.com/netdsg/cc338230a46f8a01c1b25983d5935143&lt;/a&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cybersecurity</category>
      <category>linux</category>
      <category>security</category>
    </item>
    <item>
      <title>NGINX Log Taming - Structured Data &amp; Statistical Analysis</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 25 Oct 2026 00:42:54 +0000</pubDate>
      <link>https://dev.to/netdsg/nginx-log-taming-4l4d</link>
      <guid>https://dev.to/netdsg/nginx-log-taming-4l4d</guid>
      <description>&lt;p&gt;Logs are great!  They provide a lot of information that assists in understanding the current environment and provide insight when trying to solve problems.  It seems every application has its own logging style.  Some log to a file, some log to the system journal, some log to standard out, etc.  In addition to the variance in where they log, they generally have their own logging format.  Some attempt to log in plain English while some log using application specific codes that must be cross referenced in order to understand what is happening.&lt;/p&gt;

&lt;p&gt;At some point logging collided with the concept of structured data and their was much rejoicing!  Suddenly we could shelve our custom parsers, as proud of them as we were, and enjoy logs that have predictable key/value pairs.  The most common way applications offer logs in a structured data format is via JSON output.&lt;/p&gt;

&lt;p&gt;Not only are JSONified logs wonderful for scripts that ingest them, when we are hacking for our lives during an outage we can pipe the log file through a utility such as jq and find inspiration to keep living during dark times.&lt;/p&gt;

&lt;p&gt;Now, on to the namesake of this article, NGINX!  A lot of readers here may admin NGINX servers that produce giga bytes of logs every day.  processing large logs for meaningful information can be challenging. NGINX has an option to change the logging format to JSON versus the difficult to read/parse default format. This maneuver involves creating a custom &lt;code&gt;log_format&lt;/code&gt; and then associating it with the &lt;code&gt;access_log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The custom log_format definition must be in the &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; file, while the &lt;code&gt;access_log&lt;/code&gt; directive that invokes it may be in the same file for global effect, or in an a specific site file for more granular control of logs.&lt;/p&gt;

&lt;p&gt;The log_format definition belongs in the http context of the &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; file.  Here is an example custom &lt;code&gt;log_format&lt;/code&gt; that is named &lt;code&gt;custom_json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="s"&gt;snip&lt;/span&gt; &lt;span class="s"&gt;--&amp;gt;&lt;/span&gt;

    &lt;span class="s"&gt;log_format&lt;/span&gt; &lt;span class="s"&gt;custom_json&lt;/span&gt; &lt;span class="s"&gt;escape=json&lt;/span&gt; &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;'&lt;/span&gt;
        &lt;span class="s"&gt;'"time_local":"&lt;/span&gt;&lt;span class="nv"&gt;$time_local&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"remote_addr":"&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"host":"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"request":"&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"status":&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"body_bytes_sent":"&lt;/span&gt;&lt;span class="nv"&gt;$body_bytes_sent&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"request_time":"&lt;/span&gt;&lt;span class="nv"&gt;$request_time&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"http_referrer":"&lt;/span&gt;&lt;span class="nv"&gt;$http_referer&lt;/span&gt;&lt;span class="s"&gt;",'&lt;/span&gt;
        &lt;span class="s"&gt;'"http_user_agent":"&lt;/span&gt;&lt;span class="nv"&gt;$http_user_agent&lt;/span&gt;&lt;span class="s"&gt;"'&lt;/span&gt;
    &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="s"&gt;snip&lt;/span&gt; &lt;span class="s"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we invoke the custom parser, this can be defined in the http, server, or location contexts.  Here is an invocation example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;access_log /var/log/nginx/access.log custom_json;&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Once these changes have been made, restart the service, and have a look at the new log format.  Now a line in the log file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@pagoda:/var/log/nginx# tail -1 access.log
{"time_local":"25/Oct/2026:08:30:50 +0900","remote_addr":"192.168.82.1","host":"activebit.net","request":"GET / HTTP/1.0","status": "200","body_bytes_sent":"65","request_time":"0.000","http_referrer":"","http_user_agent":""}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is arguably harder on the eyes than the default format, but if we pipe it to a utility such as jq in renders in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@pagoda:/var/log/nginx# tail -1 access.log | jq .
{
  "time_local": "25/Oct/2026:08:30:50 +0900",
  "remote_addr": "192.168.82.1",
  "host": "activebit.net",
  "request": "GET / HTTP/1.0",
  "status": "200",
  "body_bytes_sent": "65",
  "request_time": "0.000",
  "http_referrer": "",
  "http_user_agent": ""
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier to read and quickly understand what all the parts of the log message mean.  Since it is structured data we do not have to write a custom parser that's prone to errors in order to programmatically analyze the data.  Here is an example using python3 of how to quickly get statistics for a given key seen in the log file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt; 
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pprint&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pprint&lt;/span&gt;

&lt;span class="n"&gt;subjFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;keyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getFlatFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ourFile&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;hook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ourFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;logList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFlatFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subjFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;statHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;linesProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;linesProcessed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
    &lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stats for key: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seenKey&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;seenKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;linesProcessed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seenKey&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; occurances: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statHash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;seenKey&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; percent: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an example of evaluating the 'status' key value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@pagoda:/home/chris/dev.to# ./json_log_eval.py /var/log/nginx/access.log status
stats for key: status
200 occurances: 61 percent: 29%
400 occurances: 11 percent: 5%
403 occurances: 9 percent: 4%
404 occurances: 126 percent: 60%
405 occurances: 1 percent: 0%
444 occurances: 1 percent: 0%

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fear No Log File!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>infrastructure</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Network Traffic Awareness</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 22 Oct 2026 10:41:40 +0000</pubDate>
      <link>https://dev.to/netdsg/network-traffic-awareness-48nn</link>
      <guid>https://dev.to/netdsg/network-traffic-awareness-48nn</guid>
      <description>&lt;p&gt;We have many connected devices such as phones, watches, tablets, laptops, cameras, refrigerators, air conditioners, etc; yet do we know who these devices are communicating with or how? Shouldn't we know? This train of thought inspired the 'Traffic Evaluator' project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We can know what internet hosts our devices are communicating with and how.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recording the network traffic and analyzing it reveals who our devices are communicating with.  This also gives us insight into how much our ISP can know about us.  Any organization in the 'network path' can easily harvest all this information.&lt;/p&gt;

&lt;p&gt;In order to demonstrate the depth of information that is easily available through packet analysis we need a test point between the user and the internet. Providing a VPN tunnel with a respected protocol such as WireGuard allows this tool to have such a test point.&lt;/p&gt;

&lt;p&gt;If you are interested the 'Traffic Evaluator' project is at &lt;a href="https://activebit.net/te" rel="noopener noreferrer"&gt;https://activebit.net/te&lt;/a&gt; .  This site allows a user to download and install a WireGuard tunnel configuration where all the traffic is under surveillance and a usage report can be generated at the users request.  The report contains details such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organizations the connected device is communicating with&lt;/li&gt;
&lt;li&gt;Countries where the remote servers are located&lt;/li&gt;
&lt;li&gt;Second Level Domain Summary&lt;/li&gt;
&lt;li&gt;FQDNs seen in dns requests, TLS &amp;amp; QUIC Headers&lt;/li&gt;
&lt;li&gt;Understanding of what protocols are in use&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>networking</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Peer Review - Another Dimension to Consider</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Wed, 21 Oct 2026 08:38:31 +0000</pubDate>
      <link>https://dev.to/netdsg/peer-review-another-dimension-to-consider-3e2a</link>
      <guid>https://dev.to/netdsg/peer-review-another-dimension-to-consider-3e2a</guid>
      <description>&lt;p&gt;When people think of peer review it is often associated with reducing risk for upcoming changes. While this is true there is another dimension that deserves consideration.&lt;/p&gt;

&lt;p&gt;When I supported financial institutions and ISPs change control was very serious and peer review was part of that process. I found it very insightful as I learned much about my coworkers through the review.  The questions someone poses reveals much more than their answers. The peer review process created an environment that allowed us to better understand our coworkers and suddenly we could realize and respect the depth of their experience, insight, and ingenuity in a real way. This process creates TEAM!&lt;/p&gt;

&lt;p&gt;My path is somewhat opposite of others in that I moved from 'stable' organizations to startups. I found that change control and consequently peer review was not much wanted in startups. In some cases I found this rational as a fellow in a startup may be a one person department and conducting peer review with oneself is tiresome.&lt;/p&gt;

&lt;p&gt;When there is more than one person working in the same space or concern startups often avoid peer review in the name of speed and agility; DO NOT SLOW ME DOWN!&lt;/p&gt;

&lt;p&gt;I argue waiting a day will never kill a project and the respect that can be developed during the peer review process will in the long term be much more valuable than any 'shoot from the hip' change. Peer review is an opportunity to create a team in a natural way. Take advantage of this and become a stronger organization.&lt;/p&gt;

</description>
      <category>career</category>
      <category>management</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
