<?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>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>
