-
-
Save ahoward/d25e2a39dbd2cd1c12dcb4510cea9531 to your computer and use it in GitHub Desktop.
why add an exception tracker to your rails (or ruby) app's dependency list when you already have s3?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# file: app/controllers/concerns/s3_errors.rb | |
=begin | |
TL;DR; | |
class ApplicationController | |
include S3Errors | |
end | |
=end | |
module S3Errors | |
extend ActiveSupport::Concern | |
included do | |
rescue_from StandardError, with: :upload_error_to_s3 | |
end | |
def S3Errors.rotate!(cutoff: nil) | |
cutoff ||= 1.week.ago.beginning_of_day | |
re = %r{errors/(\d{4})/(\d{2})/(\d{2})/} | |
n = 0 | |
S3.list('errors/') do |key| | |
if((match = key.match(re))) | |
yyyy, mm, dd = match[1].to_i, match[2].to_i, match[3].to_i | |
date = Date.new(yyyy, mm, dd) | |
if date < cutoff.to_date | |
S3.delete(key) | |
n += 1 | |
end | |
end | |
end | |
return n | |
end | |
AutoRotater = Thread.new do | |
loop do | |
begin | |
sleep 1.minute | |
S3Errors.rotate! | |
rescue => error | |
Rails.logger.error(error) | |
end | |
sleep 59.minutes | |
end | |
end | |
private | |
def upload_error_to_s3(error) | |
uuid = SecureRandom.uuid_v7.to_s | |
now = Time.now.utc | |
path = "errors/#{now.strftime('%Y/%m/%d')}/#{uuid}.json" | |
data = { | |
class: error.class.name, | |
message: error.message, | |
backtrace: error.backtrace, | |
created_at: now.iso8601(2) | |
} | |
json = data.to_json | |
S3.write(path, json) | |
raise error | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# file: lib/s3.rb | |
require 'aws-sdk-s3' | |
require 'mime/types' | |
module S3 | |
def config | |
@config ||= Hash.new | |
end | |
def config=(config) | |
@config = config | |
end | |
def config_for(key) | |
config.fetch(key.to_s) { config.fetch(key.to_sym) } | |
end | |
def region | |
config_for(:region) | |
end | |
def access_key_id | |
config_for(:access_key_id) | |
end | |
def secret_access_key | |
config_for(:secret_access_key) | |
end | |
def bucket | |
config_for(:bucket) | |
end | |
def client | |
Aws::S3::Client.new( | |
region:, | |
access_key_id:, | |
secret_access_key:, | |
) | |
end | |
def url_for(key) | |
"https://#{bucket}.s3.amazonaws.com/#{key}" | |
end | |
def write(key, body, **kws) | |
kws[:content_type] ||= MIME::Types.type_for(key).first.to_s | |
kws[:content_disposition] ||= 'inline' | |
args = { | |
bucket:, | |
key:, | |
body:, | |
**kws | |
} | |
obj = client.put_object(**args) | |
url = url_for(key) | |
{ key:, url: }.update(args) | |
end | |
alias_method :put, :write | |
def read(key, &block) | |
obj = client.get_object(bucket:, key: key) | |
if block | |
while (chunk = obj.body.read(1024)) | |
block.call(chunk) | |
end | |
else | |
obj.body.string | |
end | |
end | |
alias_method :get, :read | |
def list(prefix = '', limit: nil, &block) | |
args = { bucket:, prefix: } | |
accum = [] | |
n = 0 | |
client.list_objects_v2(**args).each do |response| | |
response.contents.each do |obj| | |
key = obj.key | |
block ? block.call(key) : accum.push(key) | |
n += 1 | |
break if limit && (n >= limit) | |
end | |
break if limit && (n >= limit) | |
end | |
block ? nil : accum | |
end | |
alias_method :ls, :list | |
def delete(key, *keys) | |
keys.unshift(key) | |
keys.flatten! | |
keys.compact! | |
to_delete = keys.map { { key: } } | |
to_delete.each_slice(1000) do |objects| | |
args = { bucket:, delete: { objects: } } | |
client.delete_objects(args) | |
end | |
keys | |
end | |
alias_method :rm, :delete | |
def exist(key) | |
begin | |
client.head_object(bucket:, key: key) | |
true | |
rescue Aws::S3::Errors::NotFound => e | |
false | |
end | |
end | |
alias_method :exist?, :exist | |
alias_method :exists?, :exist | |
def fetch(key, &block) | |
begin | |
read(key) | |
rescue Aws::S3::Errors::NoSuchKey => _error | |
block.call.tap { |obj| write(key, obj) } | |
end | |
end | |
extend self | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# file: config/initializers/s3.rb | |
require Rails.root.join('lib/s3.rb') | |
S3.config = Rails.application.credentials.fetch(:aws) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment