# Terminal
bin/rails middleware
touch tmp/caching-dev.txt
# Web Application
# config/application.rb
require_relative "boot"
require "rails/all"
Bundler.require(*Rails.groups)
require './lib/middlewares/main.rb'
module TrackerExample
class Application < Rails::Application
config.load_defaults 7.0
config.middleware.use CaptureExceptions
end
end
# Web Application
# lib/middlewares/main.rb
require_relative 'capture_exceptions.rb'
# Web Application
# lib/middlewares/capture_exceptions.rb
require 'net/http'
class CaptureExceptions
def initialize(app)
@app = app
end
def call(env)
begin
@app.call(env)
rescue StandardError => e
@exception = e
capture_exceptions(env)
raise e
end
end
private
def capture_exceptions(env)
uri = URI('http://localhost:3001/record_exceptions')
Net::HTTP.post_form(uri,
message: @exception.message,
backtrace: @exception.backtrace[0..5].join("\n"),
source_location: source_location,
method: env['REQUEST_METHOD'],
uri: env['REQUEST_URL']
)
end
def source_location
lines[start_line..end_line].join
end
def source
@exception.backtrace.first.split(':')
end
def file_location
source[0]
end
def start_line
[source[1].to_i - 5, 0].max
end
def end_line
source[1].to_i + 5
end
def lines
File.readlines(file_location)
end
end
# Tracker Application
# config/routes.rb
Rails.application.routes.draw do
resource :record_exceptions, only: :create
root to: 'welcome#index'
end
# Tracker Application
# app/controllers/record_exceptions_controller.rb
class RecordExceptionsController < ApplicationController
skip_before_action :verify_authenticity_token
def create
Rails.cache.write((Time.current.to_f * 1000), payload)
head :ok
end
private
def payload
{
message: params[:message],
backtrace: params[:backtrace],
method: params[:method],
uri: params[:uri],
source_location: params[:source_location]
}.to_json
end
end
# Tracker Application
# views/welcome/index.html.erb
<% Rails.cache.instance_variable_get(:@data).keys.map { |key| JSON.parse(Rails.cache.fetch(key)).merge(time: key) }.reverse.each do |error| %>
<strong>Method:</strong> <%= error["method"] %><br>
<strong>URI:</strong> <%= error["uri"] %><br>
<strong>Time:</strong> <%= Time.at(error[:time].to_i / 1000) %>
<strong>Backtrace:</strong> <%= raw error["backtrace"].split("\n").join("<br>") %>
<strong>Source Location:</strong> <%= error["source_location"] %>
<% end %>
# Tracker Application
# views/welcome/index.html.erb
<div class="accordion accordion-flush" id="accordionExample">
<% Rails.cache.instance_variable_get(:@data).keys.map { |key| JSON.parse(Rails.cache.fetch(key)).merge(time: key) }.reverse.each do |error| %>
<div class="accordion-item">
<h2 class="accordion-header" id="heading<%= error[:time].to_i %>">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#body<%= error[:time].to_i %>">
<%= error["message"] %>
</button>
</h2>
<div id="body<%= error[:time].to_i %>" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<div class="card border-primary mb-3">
<div class="card-header">Info</div>
<div class="card-body text-primary">
<p class="card-text">
<strong>Method</strong> <%= error["method"] %><br>
<strong>URI</strong> <%= error["uri"] %><br>
<strong>Time</strong> <%= Time.at(error[:time].to_i / 1000) %>
</p>
</div>
</div>
<div class="card border-primary mb-3">
<div class="card-header">Backtrace</div>
<div class="card-body text-primary">
<p class="card-text">
<pre><%= raw error["backtrace"].split("\n").join("<br>") %></pre>
</p>
</div>
</div>
<div class="card border-primary mb-3">
<div class="card-header">
Source Location:
<%= error["backtrace"].split("\n").first %>
</div>
<div class="card-body text-primary">
<p class="card-text">
<pre><%= error["source_location"] %></pre>
</p>
</div>
</div>
</div>
</div>
</div>
<% end %>
</div>