5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-25 03:06:21 +00:00

mini airbrake para poder hacer seguimiento de js

This commit is contained in:
f 2020-12-07 13:21:46 -03:00
parent cc6efb095b
commit 42c7e7006a
11 changed files with 249 additions and 0 deletions

View file

@ -22,3 +22,7 @@ DEBUG_FORMS=
COOKIE_DURATION=30
# Dominio de la tienda
TIENDA=tienda.sutty.local
# Obtener esto con Site.find_by_name('panel').airbrake_api_key
PANEL_URL=https://panel.sutty.nl
AIRBRAKE_SITE_ID=1
AIRBRAKE_API_KEY=

View file

@ -86,6 +86,11 @@ gem 'rack-mini-profiler'
gem 'stackprof'
gem 'prometheus_exporter'
# debug
gem 'fast_jsonparser'
gem 'down'
gem 'sourcemap'
group :themes do
gem 'adhesiones-jekyll-theme', require: false
gem 'editorial-autogestiva-jekyll-theme', require: false

View file

@ -161,6 +161,8 @@ GEM
dotenv-rails (2.7.6)
dotenv (= 2.7.6)
railties (>= 3.2)
down (5.2.0)
addressable (~> 2.5)
editorial-autogestiva-jekyll-theme (0.2.8)
jekyll (~> 4.0)
jekyll-data (~> 1.1)
@ -190,6 +192,7 @@ GEM
factory_bot (~> 6.1.0)
railties (>= 5.0.0)
fast_blank (1.0.0)
fast_jsonparser (0.5.0)
ffi (1.13.1)
flamegraph (0.9.5)
forwardable-extended (2.6.0)
@ -479,6 +482,7 @@ GEM
jekyll-seo-tag (~> 2.1)
simpleidn (0.1.1)
unf (~> 0.1.4)
sourcemap (0.1.1)
spring (2.1.1)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
@ -578,11 +582,13 @@ DEPENDENCIES
devise-i18n
devise_invitable
dotenv-rails
down
editorial-autogestiva-jekyll-theme
email_address
exception_notification
factory_bot_rails
fast_blank
fast_jsonparser
flamegraph
friendly_id
haml-lint
@ -624,6 +630,7 @@ DEPENDENCIES
sassc-rails
selenium-webdriver
share-to-fediverse-jekyll-theme
sourcemap
spring
spring-watcher-listen (~> 2.0.0)
sqlite3

View file

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Api
module V1
# Recibe notificaciones desde Airbrake
#
# TODO: Autenticación
class NoticesController < BaseController
skip_before_action :verify_authenticity_token
# Generar un stacktrace en segundo plano y enviarlo por correo
# solo si la API key es verificable. Del otro lado siempre
# respondemos con lo mismo.
def create
if verify_api_key
BacktraceJob.perform_later site_id: params[:site_id],
errors: airbrake_params.map(&:permit!).map(&:to_h)
end
render status: 201, json: { id: 1, url: root_url }
end
private
# XXX: Por alguna razón Airbrake envía los datos con Content-Type:
# text/plain.
def airbrake_params
@airbrake_params ||= params.merge!(JSON.parse(request.raw_post) || {}).require(:errors)
end
def site
@site ||= Site.find(params[:site_id])
end
def verify_api_key
site.verifier.verify(airbrake_token, purpose: :airbrake) === Site::Api::AIRBRAKE_SECRET
rescue ActiveSupport::MessageVerifier::InvalidSignature
false
end
def airbrake_token
@airbrake_token ||= params[:key]
end
end
end
end

View file

@ -15,6 +15,14 @@
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
import { Notifier } from '@airbrake/browser'
window.airbrake = new Notifier({
projectId: process.env.AIRBRAKE_SITE_ID,
projectKey: process.env.AIRBRAKE_API_KEY,
host: process.env.PANEL_URL
})
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import 'controllers'

58
app/jobs/backtrace_job.rb Normal file
View file

@ -0,0 +1,58 @@
# frozen_string_literal: true
# Procesa los errores de JavaScript
class BacktraceJob < ApplicationJob
queue_as :low_priority
attr_reader :errors
def perform(site_id:, errors:)
@errors = errors
errors.each do |error|
error['backtrace'].each do |backtrace|
offset = SourceMap::Offset.new(backtrace['line'], backtrace['column'])
mapping = sourcemap.bsearch(offset)
next unless mapping
backtrace['file'] = mapping.source
backtrace['line'] = mapping.original.line
backtrace['column'] = mapping.original.column
end
end
begin
raise NoMethodError
rescue NoMethodError => e
ExceptionNotifier.notify_exception(e, data: { errors: errors })
end
end
private
# Obtiene todos los archivos del backtrace
def files
@files ||= errors.map { |x| x['backtrace'] }.flatten.map { |x| x['file'].split('@').last }.uniq
end
# Asume que todos los sourcemaps comparten la misma URL, lo
# correcto sería buscarlo en sourceMappingURL al final de cada
# archivo.
#
# Descarga los archivos y obtiene el backtrace original.
def sourcemap
@sourcemap ||=
begin
files.map { |x| "#{x}.map" }.map do |x|
data = FastJsonparser.parse(Rails.cache.fetch(x, expires_in: 12.hours) do
Down.open(x).read
end, symbolize_keys: false)
SourceMap::Map.from_hash data
rescue Down::Error, FastJsonparser::Error
SourceMap::Map.new
end.reduce(&:+)
end
end
end

View file

@ -6,6 +6,7 @@ class Site < ApplicationRecord
include FriendlyId
include Site::Forms
include Site::FindAndReplace
include Site::Api
include Tienda
# Cifrar la llave privada que cifra y decifra campos ocultos. Sutty

30
app/models/site/api.rb Normal file
View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
class Site
module Api
extend ActiveSupport::Concern
AIRBRAKE_SECRET = 'an api key for airbrake'
included do
encrypts :api_key
before_save :add_api_key_if_missing!
# Genera mensajes secretos que podemos usar para la API de cada sitio.
def verifier
@verifier ||= ActiveSupport::MessageVerifier.new api_key
end
def airbrake_api_key
@airbrake_api_key ||= verifier.generate(AIRBRAKE_SECRET, purpose: :airbrake)
end
private
# Asegurarse que el sitio tenga una llave para la API
def add_api_key_if_missing!
self.api_key ||= SecureRandom.hex(64)
end
end
end
end

View file

@ -35,6 +35,8 @@ Rails.application.routes.draw do
# Obtener archivos estáticos desde el directorio público
get '/sites/:site_id/static_file/(*file)', to: 'sites#static_file', as: 'site_static_file', constraints: { site_id: %r{[^/]+} }
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]
resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
# Gestionar actualizaciones del sitio
get 'pull', to: 'sites#fetch'

View file

@ -2,6 +2,7 @@
"name": "sutty",
"private": true,
"dependencies": {
"@airbrake/browser": "^1.4.1",
"@rails/actiontext": "^6.0.0",
"@rails/webpacker": "5.2.1",
"commonmark": "^0.29.0",

View file

@ -2,6 +2,18 @@
# yarn lockfile v1
"@airbrake/browser@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@airbrake/browser/-/browser-1.4.1.tgz#c0832eed3096498e51ff947e1e35bda23021a7be"
integrity sha512-4KChn2eGDllwqYJ4c6MFqIiJ2RZgg49inYiIsdpAj5MLtOpZRlPF8MsOSLGHxkEDX6u5M9KRidUfktuQ/C9lKA==
dependencies:
"@types/promise-polyfill" "^6.0.3"
"@types/request" "2.48.5"
cross-fetch "^3.0.4"
error-stack-parser "^2.0.4"
promise-polyfill "^8.1.3"
tdigest "^0.1.1"
"@babel/code-frame@^7.0.0":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
@ -983,6 +995,11 @@
resolved "https://registry.yarnpkg.com/@stimulus/webpack-helpers/-/webpack-helpers-1.1.1.tgz#eff60cd4e58b921d1a2764dc5215f5141510f2c2"
integrity sha512-XOkqSw53N9072FLHvpLM25PIwy+ndkSSbnTtjKuyzsv8K5yfkFB2rv68jU1pzqYa9FZLcvZWP4yazC0V38dx9A==
"@types/caseless@*":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@ -1017,11 +1034,31 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/promise-polyfill@^6.0.3":
version "6.0.3"
resolved "https://registry.yarnpkg.com/@types/promise-polyfill/-/promise-polyfill-6.0.3.tgz#e2f38fcd244a9e0df2cc7528e0711abcbc707b5e"
integrity sha512-f/BFgF9a+cgsMseC7rpv9+9TAE3YNjhfYrtwCo/pIeCDDfQtE6PY0b5bao2eIIEpZCBUy8Y5ToXd4ObjPSJuFw==
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/request@2.48.5":
version "2.48.5"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0"
integrity sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==
dependencies:
"@types/caseless" "*"
"@types/node" "*"
"@types/tough-cookie" "*"
form-data "^2.5.0"
"@types/tough-cookie@*":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d"
integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@ -1533,6 +1570,11 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
bintrees@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
block-stream@*:
version "0.0.9"
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@ -2261,6 +2303,13 @@ crelt@^1.0.0:
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.4.tgz#9a05d7829aedf79538f2b26f7de319cf45a25b47"
integrity sha512-l1cwMUOssGLEj5zgbut4lxJq95ZabOXVZnDybNqQRUtXh1lvUK7e7kJNm8SfvTQzYpE3AVJhIVUJKf382lMA7A==
cross-fetch@^3.0.4:
version "3.0.6"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==
dependencies:
node-fetch "2.6.1"
cross-spawn@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
@ -2832,6 +2881,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
error-stack-parser@^2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==
dependencies:
stackframe "^1.1.1"
es-abstract@^1.12.0, es-abstract@^1.5.1:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
@ -3199,6 +3255,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@ -4840,6 +4905,11 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@ -6167,6 +6237,11 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
promise-polyfill@^8.1.3:
version "8.2.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
prosemirror-commands@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.4.tgz#991563e67623acab4f8c510fad1570f8b4693780"
@ -7241,6 +7316,11 @@ stable@^0.1.8:
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stackframe@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@ -7497,6 +7577,13 @@ tar@^6.0.2:
mkdirp "^1.0.3"
yallist "^4.0.0"
tdigest@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021"
integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=
dependencies:
bintrees "1.0.1"
terser-webpack-plugin@^1.4.3:
version "1.4.5"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"