<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Nxlog on Jason Wilder&#39;s Blog</title>
        <generator uri="https://gohugo.io">Hugo</generator>
        <link>http://jasonwilder.com/categories/nxlog/</link>
        
        <language>en-us</language>  
        <updated>Mon, 22 Jul 2013 00:00:00 UTC</updated>
        
        <item>
            <title>Realtime Web Server Log Metrics</title>
            <link>http://jasonwilder.com/blog/2013/07/22/realtime-web-server-log-metrics/</link>
            <pubDate>Mon, 22 Jul 2013 00:00:00 UTC</pubDate>
            
            <guid>http://jasonwilder.com/blog/2013/07/22/realtime-web-server-log-metrics/</guid>
            <description>&lt;p&gt;This is a sample config that uses &lt;a href=&#34;http://nxlog-ce.sourceforge.net&#34;&gt;nxlog&lt;/a&gt; to tail web access logs in
Combined Log Format, pull out the status code and bytes sent and send them to statsd so they can be graphed
using Graphite.&lt;/p&gt;

&lt;p&gt;It&amp;rsquo;s a simple way to see if your web server is returning errors over time or how much data it&amp;rsquo;s sending.  The same
concept could be used for other log files.&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/etsy/logster&#34;&gt;Logster&lt;/a&gt; lets you do similar things but custom parsing
is accomplished by writing Python plugins which can be a little more complicated than using configuration
files.&lt;/p&gt;

&lt;p&gt;nxlog works by defining inputs, processors, outputs and routes that tie the inputs to processers and finally to outputs.&lt;/p&gt;

&lt;p&gt;For this example, I use the &lt;a href=&#34;http://nxlog-ce.sourceforge.net/nxlog-docs/en/nxlog-reference-manual.html#im_file&#34;&gt;im&lt;em&gt;file&lt;/a&gt;
module which will tail one or more files send each line along for processing.  nxlog will remember the last line read
by default so restarts or connectivity issues are handled gracefully.  After each line is read, the
line is passed to a _Exec&lt;/em&gt; statement that parses each field into variables.&lt;/p&gt;

&lt;p&gt;The parsing could be done in the processor but because of how the routes are setup, I chose to do it during input so
it only happens once.&lt;/p&gt;

&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span class=&#34;c&#34;&gt;# Tail access log in Combined Log Format and parse out the fields.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Input&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;in_nginx&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;  im_file
    &lt;span class=&#34;nb&#34;&gt;File&lt;/span&gt;	&lt;span class=&#34;s2&#34;&gt;&amp;quot;/var/log/nginx/access.log&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;    if $raw_event =~ /^(\S+) (\S+) (\S+) \[([^\]]+)\] \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;(\S+) (.+) HTTP.\d\.\d\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt; (\d+) (\d+) \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;([^\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;]+)\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt; \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;([^\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;]+)\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;/\
                { \
                  $Hostname = $1; \
                  if $3 != &amp;#39;-&amp;#39; $AccountName = $3; \
                  $EventTime = parsedate($4); \
                  $HTTPMethod = $5; \
                  $HTTPURL = $6; \
                  $HTTPResponseStatus = $7; \
                  $HTTPBytesSent = $8; \
                  $HTTPReferer = $9; \
                  $HTTPUserAgent = $10; \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Input&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;There are two processors configured &lt;em&gt;http_status&lt;/em&gt; and &lt;em&gt;http_bytes&lt;/em&gt;.  Each one checks to make sure the required
data has been parsed and then rewrites the event into a &lt;a href=&#34;https://github.com/b/statsd_spec&#34;&gt;statsd protocol&lt;/a&gt;
metric.  Both processors create a counter metric that statsd will sum up and roll-over at every interval.&lt;/p&gt;

&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span class=&#34;c&#34;&gt;# Rewrite the log message to a statsd counter event using the HTTP status code.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Processor&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http_status&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;      pm_null
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;        if defined($HTTPResponseStatus) { \
		          $raw_event = &lt;span class=&#34;s2&#34;&gt;&amp;quot;http.status.&amp;quot;&lt;/span&gt; + $HTTPResponseStatus + &lt;span class=&#34;s2&#34;&gt;&amp;quot;:1|c&amp;quot;&lt;/span&gt;; \
                } else { \
                  drop(); \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Processor&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Rewrite the log message to a statsd counter event using the bytes sent.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Processor&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http_bytes&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;      pm_null
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;        if defined($HTTPBytesSent) { \
		          $raw_event = &lt;span class=&#34;s2&#34;&gt;&amp;quot;http.bytes.&amp;quot;&lt;/span&gt; + $HTTPBytes + &lt;span class=&#34;s2&#34;&gt;&amp;quot;:&amp;quot;&lt;/span&gt; + $HTTPBytesSent + &lt;span class=&#34;s2&#34;&gt;&amp;quot;|c&amp;quot;&lt;/span&gt;; \
                } else { \
                  drop(); \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Processor&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;The output is a &lt;a href=&#34;http://nxlog-ce.sourceforge.net/nxlog-docs/en/nxlog-reference-manual.html#om_udp&#34;&gt;om_udp&lt;/a&gt;
that forwards the re-written log event to a statsd instance.&lt;/p&gt;

&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span class=&#34;c&#34;&gt;# Statsd uses UDP&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Output&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;out_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;	om_udp
    &lt;span class=&#34;nb&#34;&gt;Host&lt;/span&gt;	&lt;span class=&#34;m&#34;&gt;127.0.0.1&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Port&lt;/span&gt;	&lt;span class=&#34;m&#34;&gt;8125&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Output&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;Finally, two routes are created that originate from the same input file but are sent to separate processors and back
to the same output.  Since processors are normally chained together and I&amp;rsquo;m re-writing the event, chaining them
together doesn&amp;rsquo;t work.  Instead I need a new copy of the log message for each processor.&lt;/p&gt;

&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span class=&#34;c&#34;&gt;# Route nginx access log through status processor and out to statsd&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Route&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;web_status_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Path&lt;/span&gt;        in_nginx =&amp;gt; http_status =&amp;gt; out_statsd
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Route&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Since we re-wrote the log event, define a new route for bytes sent&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Route&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;web_bytes_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Path&lt;/span&gt;        in_nginx =&amp;gt; http_bytes =&amp;gt; out_statsd
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Route&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;And the whole thing put together:&lt;/p&gt;

&lt;p&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span class=&#34;c&#34;&gt;# Tail access log in Combined Log Format and parse out the fields.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Input&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;in_nginx&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;  im_file
    &lt;span class=&#34;nb&#34;&gt;File&lt;/span&gt;	&lt;span class=&#34;s2&#34;&gt;&amp;quot;/var/log/nginx/access.log&amp;quot;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;    if $raw_event =~ /^(\S+) (\S+) (\S+) \[([^\]]+)\] \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;(\S+) (.+) HTTP.\d\.\d\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt; (\d+) (\d+) \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;([^\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;]+)\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt; \&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;([^\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;]+)\&lt;span class=&#34;err&#34;&gt;&amp;quot;&lt;/span&gt;/\
                { \
                  $Hostname = $1; \
                  if $3 != &amp;#39;-&amp;#39; $AccountName = $3; \
                  $EventTime = parsedate($4); \
                  $HTTPMethod = $5; \
                  $HTTPURL = $6; \
                  $HTTPResponseStatus = $7; \
                  $HTTPBytesSent = $8; \
                  $HTTPReferer = $9; \
                  $HTTPUserAgent = $10; \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Input&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Rewrite the log message to a statsd counter event using the HTTP status code.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Processor&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http_status&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;      pm_null
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;        if defined($HTTPResponseStatus) { \
		          $raw_event = &lt;span class=&#34;s2&#34;&gt;&amp;quot;http.status.&amp;quot;&lt;/span&gt; + $HTTPResponseStatus + &lt;span class=&#34;s2&#34;&gt;&amp;quot;:1|c&amp;quot;&lt;/span&gt;; \
                } else { \
                  drop(); \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Processor&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Rewrite the log message to a statsd counter event using the bytes sent.&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Processor&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http_bytes&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;      pm_null
    &lt;span class=&#34;nb&#34;&gt;Exec&lt;/span&gt;        if defined($HTTPBytesSent) { \
		          $raw_event = &lt;span class=&#34;s2&#34;&gt;&amp;quot;http.bytes.&amp;quot;&lt;/span&gt; + $HTTPBytes + &lt;span class=&#34;s2&#34;&gt;&amp;quot;:&amp;quot;&lt;/span&gt; + $HTTPBytesSent + &lt;span class=&#34;s2&#34;&gt;&amp;quot;|c&amp;quot;&lt;/span&gt;; \
                } else { \
                  drop(); \
                }
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Processor&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Statsd uses UDP&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Output&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;out_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Module&lt;/span&gt;	om_udp
    &lt;span class=&#34;nb&#34;&gt;Host&lt;/span&gt;	&lt;span class=&#34;m&#34;&gt;127.0.0.1&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Port&lt;/span&gt;	&lt;span class=&#34;m&#34;&gt;8125&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Output&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Route nginx access log through status processor and out to statsd&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Route&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;web_status_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Path&lt;/span&gt;        in_nginx =&amp;gt; http_status =&amp;gt; out_statsd
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Route&amp;gt;&lt;/span&gt;

&lt;span class=&#34;c&#34;&gt;# Since we re-wrote the log event, define a new route for bytes sent&lt;/span&gt;
&lt;span class=&#34;nt&#34;&gt;&amp;lt;Route&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;web_bytes_statsd&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&#34;nb&#34;&gt;Path&lt;/span&gt;        in_nginx =&amp;gt; http_bytes =&amp;gt; out_statsd
&lt;span class=&#34;nt&#34;&gt;&amp;lt;/Route&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
