mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 16:38:23 +00:00
Add Sentry monitoring for previously unreported errors (#1066)
* Add Sentry monitoring for previously unreported errors * Fix * Fixes * whoops!
This commit is contained in:
parent
922e7384c0
commit
28fa174861
42 changed files with 118 additions and 97 deletions
|
|
@ -268,8 +268,7 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController
|
|||
queue_project_mapping(heartbeat[:project])
|
||||
results << [ new_heartbeat.attributes, 201 ]
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("Error creating heartbeat: #{e.class.name} #{e.message}")
|
||||
report_error(e, message: "Error creating heartbeat")
|
||||
results << [ { error: e.message, type: e.class.name }, 422 ]
|
||||
end
|
||||
|
||||
|
|
@ -284,7 +283,7 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController
|
|||
end
|
||||
rescue => e
|
||||
# never raise an error here because it will break the heartbeat flow
|
||||
Rails.logger.error("Error queuing project mapping: #{e.class.name} #{e.message}")
|
||||
report_error(e, message: "Error queuing project mapping")
|
||||
end
|
||||
|
||||
def check_lockout
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ module Api
|
|||
render json: { error: user.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error "Error creating user from external Slack data: #{e.message}"
|
||||
report_error(e, message: "Error creating user from external Slack data")
|
||||
render json: { error: "Internal server error" }, status: :internal_server_error
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -278,8 +278,7 @@ class Api::V1::StatsController < ApplicationController
|
|||
|
||||
JSON.parse(response.body)["user"]["id"]
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("Error finding user by email: #{e}")
|
||||
report_error(e, message: "Error finding user by email")
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
include ErrorReporting
|
||||
|
||||
before_action :set_paper_trail_whodunnit
|
||||
before_action :sentry_context, if: :current_user
|
||||
before_action :initialize_cache_counters
|
||||
|
|
|
|||
19
app/controllers/concerns/error_reporting.rb
Normal file
19
app/controllers/concerns/error_reporting.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ErrorReporting
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Prefer this over calling Sentry and logger separately to keep reporting consistent.
|
||||
# Usage: report_error(exception, message: "optional context")
|
||||
def report_error(exception, message: nil, extra: {})
|
||||
Rails.logger.error(message || exception.message)
|
||||
Sentry.capture_exception(exception, extra: extra.merge(message: message).compact)
|
||||
end
|
||||
|
||||
# Prefer this for non-exception events that still warrant Sentry visibility.
|
||||
# Usage: report_message("Something bad happened", level: :error)
|
||||
def report_message(message, level: :error, extra: {})
|
||||
Rails.logger.send(level, message)
|
||||
Sentry.capture_message(message, level: level, extra: extra)
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,7 @@ class DeletionRequestsController < ApplicationController
|
|||
@deletion_request = DeletionRequest.create_for_user!(current_user)
|
||||
redirect_to deletion_path
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Sentry.capture_exception(e)
|
||||
report_error(e, message: "Deletion request creation failed")
|
||||
redirect_to my_settings_path
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ class DocsController < InertiaController
|
|||
format.md { render plain: content, content_type: "text/markdown" }
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "Error loading docs: #{e.message}"
|
||||
report_error(e, message: "Error loading docs")
|
||||
render_not_found
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ class My::HeartbeatImportsController < ApplicationController
|
|||
rescue HeartbeatImportRunner::InvalidProviderError, ActionController::ParameterMissing => e
|
||||
redirect_with_import_error(e.message)
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("Error starting heartbeat import for user #{current_user&.id}: #{e.message}")
|
||||
report_error(e, message: "Error starting heartbeat import for user #{current_user&.id}")
|
||||
redirect_with_import_error("error reading file: #{e.message}")
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ class SessionsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
Rails.logger.error "HCA OAuth error: #{params[:error]}"
|
||||
Sentry.capture_message("HCA OAuth error: #{params[:error]}")
|
||||
report_message("HCA OAuth error: #{params[:error]}")
|
||||
redirect_to root_path, alert: "Failed to authenticate with Hack Club Auth. Error ID: #{Sentry.last_event_id}"
|
||||
return
|
||||
end
|
||||
|
|
@ -70,8 +69,7 @@ class SessionsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
Rails.logger.error "Slack OAuth error: #{params[:error]}"
|
||||
Sentry.capture_message("Slack OAuth error: #{params[:error]}")
|
||||
report_message("Slack OAuth error: #{params[:error]}")
|
||||
redirect_to root_path, alert: "Failed to authenticate with Slack. Error ID: #{Sentry.last_event_id}"
|
||||
return
|
||||
end
|
||||
|
|
@ -101,7 +99,7 @@ class SessionsController < ApplicationController
|
|||
redirect_to root_path, notice: "Successfully signed in with Slack! Welcome!"
|
||||
end
|
||||
else
|
||||
Rails.logger.error "Failed to create/update user from Slack data"
|
||||
report_message("Failed to create/update user from Slack data")
|
||||
redirect_to root_path, alert: "Failed to sign in with Slack"
|
||||
end
|
||||
end
|
||||
|
|
@ -133,8 +131,7 @@ class SessionsController < ApplicationController
|
|||
redirect_uri = url_for(action: :github_create, only_path: false)
|
||||
|
||||
if params[:error].present?
|
||||
Rails.logger.error "GitHub OAuth error: #{params[:error]}"
|
||||
Sentry.capture_message("GitHub OAuth error: #{params[:error]}")
|
||||
report_message("GitHub OAuth error: #{params[:error]}")
|
||||
redirect_to my_settings_path, alert: "Failed to authenticate with GitHub. Error ID: #{Sentry.last_event_id}"
|
||||
return
|
||||
end
|
||||
|
|
@ -150,7 +147,7 @@ class SessionsController < ApplicationController
|
|||
PosthogService.capture(@user, "github_linked")
|
||||
redirect_to my_settings_path, notice: "Successfully linked GitHub account!"
|
||||
else
|
||||
Rails.logger.error "Failed to link GitHub account"
|
||||
report_message("Failed to link GitHub account")
|
||||
redirect_to my_settings_path, alert: "Failed to link GitHub account"
|
||||
end
|
||||
end
|
||||
|
|
@ -332,13 +329,13 @@ class SessionsController < ApplicationController
|
|||
expected_nonce = session.delete(session_key)
|
||||
|
||||
if expected_nonce.blank? || received_nonce.blank?
|
||||
Rails.logger.error("#{provider} OAuth state missing expected=#{expected_nonce.present?} received=#{received_nonce.present?}")
|
||||
report_message("#{provider} OAuth state missing expected=#{expected_nonce.present?} received=#{received_nonce.present?}")
|
||||
return false
|
||||
end
|
||||
|
||||
return true if ActiveSupport::SecurityUtils.secure_compare(received_nonce.to_s, expected_nonce.to_s)
|
||||
|
||||
Rails.logger.error("#{provider} OAuth state mismatch")
|
||||
report_message("#{provider} OAuth state mismatch")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class Settings::AccessController < Settings::BaseController
|
|||
render json: { token: new_api_key.token }, status: :ok
|
||||
end
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("error rotate #{e.class.name} #{e.message}")
|
||||
report_error(e, message: "error rotate #{e.class.name}")
|
||||
render json: { error: "cant rotate" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ class Settings::NotificationsController < Settings::BaseController
|
|||
PosthogService.capture(@user, "settings_updated", { fields: [ "weekly_summary_email_enabled" ] })
|
||||
redirect_to my_settings_notifications_path, notice: "Settings updated successfully"
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("Failed to update notification settings: #{e.message}")
|
||||
report_error(e, message: "Failed to update notification settings")
|
||||
flash.now[:error] = "Failed to update settings, sorry :("
|
||||
render_notifications(status: :unprocessable_entity)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class ApplicationJob < ActiveJob::Base
|
||||
include ErrorReporting
|
||||
|
||||
# Automatically retry jobs that encountered a deadlock
|
||||
# retry_on ActiveRecord::Deadlocked
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class AttemptProjectRepoMappingJob < ApplicationJob
|
|||
puts "repo: #{repo}"
|
||||
repo["html_url"]
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "Failed to parse GitHub repo response: #{e.message}"
|
||||
report_error(e, message: "Failed to parse GitHub repo response")
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ class AttemptProjectRepoMappingJob < ApplicationJob
|
|||
|
||||
parsed_response
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "Failed to parse GitHub orgs response: #{e.message}"
|
||||
report_error(e, message: "Failed to parse GitHub orgs response")
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -86,8 +86,7 @@ class GeocodeUsersWithoutCountryJob < ApplicationJob
|
|||
return nil unless result&.country_code.present?
|
||||
result.country_code.upcase
|
||||
rescue => e
|
||||
Rails.logger.error "geocode fail on #{ip}: #{e.message}"
|
||||
Sentry.capture_exception(e)
|
||||
report_error(e, message: "geocode fail on #{ip}")
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
@ -98,8 +97,7 @@ class GeocodeUsersWithoutCountryJob < ApplicationJob
|
|||
return nil unless tz&.tzinfo&.respond_to?(:country_code)
|
||||
tz.tzinfo.country_code&.upcase
|
||||
rescue => e
|
||||
Rails.logger.error "timezone geocode fail for #{timezone}: #{e.message}"
|
||||
Sentry.capture_exception(e)
|
||||
report_error(e, message: "timezone geocode fail for #{timezone}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class HeartbeatExportJob < ApplicationJob
|
|||
end
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
Rails.logger.error("Heartbeat export failed for user #{user_id}: #{e.message}")
|
||||
report_error(e, message: "Heartbeat export failed for user #{user_id}")
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class OneTime::SetUserTimezoneFromSlackJob < ApplicationJob
|
|||
user.set_timezone_from_slack
|
||||
user.save!
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to update timezone for user #{user.id}: #{e.message}"
|
||||
report_error(e, message: "Failed to update timezone for user #{user.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ class ProcessAccountDeletionsJob < ApplicationJob
|
|||
|
||||
Rails.logger.info "kerblamed account ##{deletion_request.user_id}"
|
||||
rescue StandardError => e
|
||||
Sentry.capture_exception(e, extra: { user_id: deletion_request.user_id })
|
||||
Rails.logger.error "failed to kerblam ##{deletion_request.user_id}: #{e.message}"
|
||||
report_error(e, message: "failed to kerblam ##{deletion_request.user_id}", extra: { user_id: deletion_request.user_id })
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class ProcessCommitJob < ApplicationJob
|
|||
# when :gitlab
|
||||
# process_gitlab_commit(user, commit_sha, commit_api_url, repository)
|
||||
else
|
||||
Rails.logger.error "[ProcessCommitJob] Unknown provider '#{provider_sym}' for commit #{commit_sha}."
|
||||
report_message("[ProcessCommitJob] Unknown provider '#{provider_sym}' for commit #{commit_sha}.")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -61,13 +61,13 @@ class ProcessCommitJob < ApplicationJob
|
|||
|
||||
api_commit_sha = commit_data_json["sha"]
|
||||
unless api_commit_sha == commit_sha
|
||||
Rails.logger.error "[ProcessCommitJob] SHA mismatch for User ##{user.id}. Expected #{commit_sha}, API returned #{api_commit_sha}. URL: #{commit_api_url}"
|
||||
report_message("[ProcessCommitJob] SHA mismatch for User ##{user.id}. Expected #{commit_sha}, API returned #{api_commit_sha}. URL: #{commit_api_url}")
|
||||
return # Critical data integrity issue
|
||||
end
|
||||
|
||||
committer_date_str = commit_data_json.dig("commit", "committer", "date")
|
||||
unless committer_date_str
|
||||
Rails.logger.error "[ProcessCommitJob] Committer date not found in API response for commit #{commit_sha}. Data: #{commit_data_json.inspect}"
|
||||
report_message("[ProcessCommitJob] Committer date not found in API response for commit #{commit_sha}.")
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -75,8 +75,8 @@ class ProcessCommitJob < ApplicationJob
|
|||
# API dates are typically ISO8601 (UTC). Time.zone.parse respects the application's zone.
|
||||
# It's good practice to store in UTC, which parse will do correctly for ISO8601.
|
||||
commit_actual_created_at = Time.zone.parse(committer_date_str)
|
||||
rescue ArgumentError
|
||||
Rails.logger.error "[ProcessCommitJob] Invalid committer date format '#{committer_date_str}' for commit #{commit_sha}."
|
||||
rescue ArgumentError => e
|
||||
report_error(e, message: "[ProcessCommitJob] Invalid committer date format '#{committer_date_str}' for commit #{commit_sha}.")
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ class ProcessCommitJob < ApplicationJob
|
|||
Rails.logger.info "[ProcessCommitJob] Successfully processed commit #{api_commit_sha} for User ##{user.id}."
|
||||
|
||||
elsif response.status.code == 401 # Unauthorized
|
||||
Rails.logger.error "[ProcessCommitJob] Unauthorized (401) for User ##{user.id}. GitHub token expired/invalid. URL: #{commit_api_url}"
|
||||
report_message("[ProcessCommitJob] Unauthorized (401) for User ##{user.id}. GitHub token expired/invalid. URL: #{commit_api_url}")
|
||||
user.update!(github_access_token: nil)
|
||||
Rails.logger.info "[ProcessCommitJob] Cleared invalid GitHub token for User ##{user.id}. User will need to re-authenticate."
|
||||
elsif response.status.code == 404
|
||||
|
|
@ -102,21 +102,21 @@ class ProcessCommitJob < ApplicationJob
|
|||
Rails.logger.warn "[ProcessCommitJob] GitHub API rate limit exceeded for User ##{user.id}. Retrying in #{delay_seconds}s. URL: #{commit_api_url}"
|
||||
self.class.set(wait: delay_seconds.seconds).perform_later(user.id, commit_sha, commit_api_url, "github", repository&.id)
|
||||
else
|
||||
Rails.logger.error "[ProcessCommitJob] GitHub API forbidden (403) for User ##{user.id}. URL: #{commit_api_url}. Response: #{response.body.to_s.truncate(500)}"
|
||||
report_message("[ProcessCommitJob] GitHub API forbidden (403) for User ##{user.id}. URL: #{commit_api_url}. Response: #{response.body.to_s.truncate(500)}")
|
||||
end
|
||||
else
|
||||
Rails.logger.error "[ProcessCommitJob] GitHub API error for User ##{user.id}. Status: #{response.status}. URL: #{commit_api_url}. Response: #{response.body.to_s.truncate(500)}"
|
||||
report_message("[ProcessCommitJob] GitHub API error for User ##{user.id}. Status: #{response.status}. URL: #{commit_api_url}. Response: #{response.body.to_s.truncate(500)}")
|
||||
raise "GitHub API Error: Status #{response.status}" if response.status.server_error? # Trigger retry for server errors
|
||||
end
|
||||
|
||||
rescue HTTP::Error => e # Covers TimeoutError, ConnectionError
|
||||
Rails.logger.error "[ProcessCommitJob] HTTP Error fetching commit #{commit_sha} for User ##{user.id}: #{e.message}. URL: #{commit_api_url}"
|
||||
report_error(e, message: "[ProcessCommitJob] HTTP Error fetching commit #{commit_sha} for User ##{user.id}. URL: #{commit_api_url}")
|
||||
raise # Re-raise to allow GoodJob to retry based on retry_on
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "[ProcessCommitJob] JSON Parse Error for commit #{commit_sha} (User ##{user.id}): #{e.message}. URL: #{commit_api_url}. Body: #{response&.body&.to_s&.truncate(200)}"
|
||||
report_error(e, message: "[ProcessCommitJob] JSON Parse Error for commit #{commit_sha} (User ##{user.id}). URL: #{commit_api_url}. Body: #{response&.body&.to_s&.truncate(200)}")
|
||||
# Malformed JSON usually isn't temporary, so might not retry unless API is known to be flaky.
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error "[ProcessCommitJob] Validation failed for commit #{commit_sha} (User ##{user.id}): #{e.message}"
|
||||
report_error(e, message: "[ProcessCommitJob] Validation failed for commit #{commit_sha} (User ##{user.id})")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class PullRepoCommitsJob < ApplicationJob
|
|||
process_commits(user, commits_data, repository)
|
||||
|
||||
elsif response.status.code == 401 # Unauthorized
|
||||
Rails.logger.error "[PullRepoCommitsJob] Unauthorized (401) for User ##{user.id}. GitHub token expired/invalid. URL: #{api_url}"
|
||||
report_message("[PullRepoCommitsJob] Unauthorized (401) for User ##{user.id}. GitHub token expired/invalid. URL: #{api_url}")
|
||||
user.update!(github_access_token: nil)
|
||||
Rails.logger.info "[PullRepoCommitsJob] Cleared invalid GitHub token for User ##{user.id}. User will need to re-authenticate."
|
||||
elsif response.status.code == 404
|
||||
|
|
@ -56,18 +56,18 @@ class PullRepoCommitsJob < ApplicationJob
|
|||
Rails.logger.warn "[PullRepoCommitsJob] GitHub API rate limit exceeded for User ##{user.id}. Retrying in #{delay_seconds}s."
|
||||
self.class.set(wait: delay_seconds.seconds).perform_later(user.id, owner, repo)
|
||||
else
|
||||
Rails.logger.error "[PullRepoCommitsJob] GitHub API forbidden (403) for User ##{user.id}. Response: #{response.body.to_s.truncate(500)}"
|
||||
report_message("[PullRepoCommitsJob] GitHub API forbidden (403) for User ##{user.id}. Response: #{response.body.to_s.truncate(500)}")
|
||||
end
|
||||
else
|
||||
Rails.logger.error "[PullRepoCommitsJob] GitHub API error for User ##{user.id}. Status: #{response.status}. Response: #{response.body.to_s.truncate(500)}"
|
||||
report_message("[PullRepoCommitsJob] GitHub API error for User ##{user.id}. Status: #{response.status}. Response: #{response.body.to_s.truncate(500)}")
|
||||
raise "GitHub API Error: Status #{response.status}" if response.status.server_error?
|
||||
end
|
||||
|
||||
rescue HTTP::Error => e
|
||||
Rails.logger.error "[PullRepoCommitsJob] HTTP Error fetching commits for #{owner}/#{repo} (User ##{user.id}): #{e.message}"
|
||||
report_error(e, message: "[PullRepoCommitsJob] HTTP Error fetching commits for #{owner}/#{repo} (User ##{user.id})")
|
||||
raise # Re-raise to allow GoodJob to retry based on retry_on
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "[PullRepoCommitsJob] JSON Parse Error for #{owner}/#{repo} (User ##{user.id}): #{e.message}"
|
||||
report_error(e, message: "[PullRepoCommitsJob] JSON Parse Error for #{owner}/#{repo} (User ##{user.id})")
|
||||
raise # Re-raise to allow GoodJob to retry based on retry_on
|
||||
end
|
||||
end
|
||||
|
|
@ -126,10 +126,10 @@ class PullRepoCommitsJob < ApplicationJob
|
|||
Rails.logger.warn "[PullRepoCommitsJob] Failed to fetch commit details for #{commit_sha}: #{commit_response.status}"
|
||||
end
|
||||
rescue HTTP::Error => e
|
||||
Rails.logger.error "[PullRepoCommitsJob] HTTP Error fetching commit details for #{commit_sha}: #{e.message}"
|
||||
report_error(e, message: "[PullRepoCommitsJob] HTTP Error fetching commit details for #{commit_sha}")
|
||||
next
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "[PullRepoCommitsJob] JSON Parse Error for commit details #{commit_sha}: #{e.message}"
|
||||
report_error(e, message: "[PullRepoCommitsJob] JSON Parse Error for commit details #{commit_sha}")
|
||||
next
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ require "http" # Make sure 'http' gem is available
|
|||
|
||||
module RepoHost
|
||||
class SyncUserEventsJob < ApplicationJob
|
||||
include ErrorReporting
|
||||
|
||||
queue_as :literally_whenever
|
||||
|
||||
# MAX_API_PAGES_TO_FETCH: Max pages to fetch. GitHub's /users/{username}/events endpoint
|
||||
|
|
@ -38,7 +40,7 @@ module RepoHost
|
|||
# when :gitlab
|
||||
# process_gitlab_events
|
||||
else
|
||||
Rails.logger.error "RepoHost::SyncUserEventsJob: Unknown provider '#{@provider_sym}' for User ##{@user.id}. Skipping."
|
||||
report_message("RepoHost::SyncUserEventsJob: Unknown provider '#{@provider_sym}' for User ##{@user.id}. Skipping.")
|
||||
return
|
||||
end
|
||||
Rails.logger.info "Finished event sync for User ##{@user.id}, Provider: #{@provider_sym}."
|
||||
|
|
@ -69,7 +71,7 @@ module RepoHost
|
|||
begin
|
||||
response = http_client_for_github.get(api_url)
|
||||
rescue HTTP::Error => e
|
||||
Rails.logger.error "RepoHost::SyncUserEventsJob: HTTP Error for User ##{@user.id} on page #{current_page}: #{e.message}"
|
||||
report_error(e, message: "RepoHost::SyncUserEventsJob: HTTP Error for User ##{@user.id} on page #{current_page}")
|
||||
break
|
||||
end
|
||||
|
||||
|
|
@ -141,7 +143,7 @@ module RepoHost
|
|||
def handle_github_api_error(response, page_number)
|
||||
error_details = response.parse rescue response.body.to_s.truncate(255)
|
||||
log_message = "RepoHost::SyncUserEventsJob: GitHub API Error for User ##{@user.id} on page #{page_number}: Status #{response.status}, Body: #{error_details}"
|
||||
Rails.logger.error log_message
|
||||
report_message(log_message)
|
||||
|
||||
case response.status.code
|
||||
when 401 # Unauthorized
|
||||
|
|
@ -158,7 +160,7 @@ module RepoHost
|
|||
when 422 # Unprocessable Entity - often if the user has been suspended
|
||||
Rails.logger.warn "GitHub API returned 422 for User ##{@user.id}. User might be suspended. Sync aborted. Details: #{error_details}"
|
||||
else
|
||||
Rails.logger.error "Unhandled GitHub API error for User ##{@user.id}: #{response.status}. Sync aborted."
|
||||
report_message("Unhandled GitHub API error for User ##{@user.id}: #{response.status}. Sync aborted.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class SailorsLogNotifyJob < ApplicationJob
|
|||
slsn.update(sent: true)
|
||||
SailorsLogTeletypeJob.perform_later(message)
|
||||
else
|
||||
Rails.logger.error("Failed to send Slack notification: #{response_data["error"]}")
|
||||
report_message("Failed to send Slack notification: #{response_data["error"]}")
|
||||
ignorable_errors = %w[channel_not_found is_archived]
|
||||
if ignorable_errors.include?(response_data["error"])
|
||||
# disable any preferences for this channel
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ class ScanRepoEventsForCommitsJob < ApplicationJob
|
|||
potential_commits_buffer.clear
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
Rails.logger.error "[ScanRepoEventsForCommitsJob] Failed to parse raw_event_payload for Event ID #{event.id}: #{e.message}"
|
||||
report_error(e, message: "[ScanRepoEventsForCommitsJob] Failed to parse raw_event_payload for Event ID #{event.id}")
|
||||
rescue => e # Catch other potential errors during event processing
|
||||
Rails.logger.error "[ScanRepoEventsForCommitsJob] Error processing Event ID #{event.id}: #{e.message}\n#{e.backtrace.take(5).join("\n")}"
|
||||
report_error(e, message: "[ScanRepoEventsForCommitsJob] Error processing Event ID #{event.id}")
|
||||
end
|
||||
|
||||
# Process any remaining commits in the buffer
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ class SetUserCountryCodeJob < ApplicationJob
|
|||
user.update!(country_code: country_code)
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "timezone geocode fail for #{@user.timezone}: #{e.message}"
|
||||
Sentry.capture_exception(e)
|
||||
report_error(e, message: "timezone geocode fail for #{@user.timezone}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -53,8 +52,7 @@ class SetUserCountryCodeJob < ApplicationJob
|
|||
result.country_code.upcase
|
||||
|
||||
rescue => e
|
||||
Rails.logger.error "geocode fail on #{ip}: #{e.message}"
|
||||
Sentry.capture_exception(e)
|
||||
report_error(e, message: "geocode fail on #{ip}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class SlackUsernameUpdateJob < ApplicationJob
|
|||
user.update_from_slack
|
||||
user.save!
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to update Slack username and avatar for user #{user.id}: #{e.message}"
|
||||
report_error(e, message: "Failed to update Slack username and avatar for user #{user.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class SyncRepoMetadataJob < ApplicationJob
|
|||
raise
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "[SyncRepoMetadataJob] Unexpected error: #{e.message}"
|
||||
report_error(e, message: "[SyncRepoMetadataJob] Unexpected error")
|
||||
raise # Retry for other errors
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class SyncStaleRepoMetadataJob < ApplicationJob
|
|||
mapping.update!(repository: repo)
|
||||
repos_to_sync[repo.id] = repo
|
||||
rescue => e
|
||||
Rails.logger.error "[SyncStaleRepoMetadataJob] Failed to create repository for mapping #{mapping.id}: #{e.message}"
|
||||
report_error(e, message: "[SyncStaleRepoMetadataJob] Failed to create repository for mapping #{mapping.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class UserSlackStatusUpdateJob < ApplicationJob
|
|||
begin
|
||||
user.update_slack_status
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to update Slack status for user #{user.slack_uid}: #{e.message}"
|
||||
report_error(e, message: "Failed to update Slack status for user #{user.slack_uid}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
module OauthAuthentication
|
||||
extend ActiveSupport::Concern
|
||||
include ErrorReporting
|
||||
|
||||
class_methods do
|
||||
include ErrorReporting
|
||||
|
||||
def hca_authorize_url(redirect_uri)
|
||||
params = {
|
||||
redirect_uri:,
|
||||
|
|
@ -132,8 +135,7 @@ module OauthAuthentication
|
|||
user.save!
|
||||
user
|
||||
rescue => e
|
||||
Rails.logger.error "Error creating user from Slack data: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
report_error(e, message: "Error creating user from Slack data: #{e.message}")
|
||||
nil
|
||||
end
|
||||
|
||||
|
|
@ -175,8 +177,7 @@ module OauthAuthentication
|
|||
|
||||
current_user
|
||||
rescue => e
|
||||
Rails.logger.error "Error linking GitHub account: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
report_error(e, message: "Error linking GitHub account: #{e.message}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ class User < ApplicationRecord
|
|||
if as_tz
|
||||
self.timezone = as_tz.name
|
||||
else
|
||||
Rails.logger.error "Invalid timezone #{timezone} for user #{id}"
|
||||
report_message("Invalid timezone #{timezone} for user #{id}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
class AnonymizeUserService
|
||||
class AnonymizeUserService < ApplicationService
|
||||
def self.call(user)
|
||||
new(user).call
|
||||
end
|
||||
|
|
@ -14,8 +14,7 @@ class AnonymizeUserService
|
|||
destroy_associated_records
|
||||
end
|
||||
rescue StandardError => e
|
||||
Sentry.capture_exception(e, extra: { user_id: user.id })
|
||||
Rails.logger.error "AnonymizeUserService failed for user #{user.id}: #{e.message}"
|
||||
report_error(e, message: "AnonymizeUserService failed for user #{user.id}", extra: { user_id: user.id })
|
||||
raise
|
||||
end
|
||||
|
||||
|
|
|
|||
3
app/services/application_service.rb
Normal file
3
app/services/application_service.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class ApplicationService
|
||||
include ErrorReporting
|
||||
end
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
class HackClubGeocoderService
|
||||
class HackClubGeocoderService < ApplicationService
|
||||
BASE_URL = "https://geocoder.hackclub.com"
|
||||
|
||||
def self.geoip(ip_address)
|
||||
|
|
@ -43,11 +43,11 @@ class HackClubGeocoderService
|
|||
if response.is_a?(Net::HTTPSuccess)
|
||||
JSON.parse(response.body)
|
||||
else
|
||||
Rails.logger.error "HackClub Geocoder API error: #{response.code} #{response.body}"
|
||||
report_message("HackClub Geocoder API error: #{response.code} #{response.body}")
|
||||
nil
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "HackClub Geocoder API request failed: #{e.message}"
|
||||
report_error(e, message: "HackClub Geocoder API request failed")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
require "fileutils"
|
||||
require "zlib"
|
||||
|
||||
class HeartbeatImportRunner
|
||||
class HeartbeatImportRunner < ApplicationService
|
||||
class << self
|
||||
include ErrorReporting
|
||||
end
|
||||
|
||||
PROGRESS_INTERVAL = 250
|
||||
REMOTE_REFRESH_THROTTLE = 5.seconds
|
||||
TMP_DIR = Rails.root.join("tmp", "heartbeat_imports")
|
||||
|
|
@ -90,7 +94,7 @@ class HeartbeatImportRunner
|
|||
|
||||
run.reload
|
||||
rescue => e
|
||||
Rails.logger.error("Error refreshing heartbeat import run #{run&.id}: #{e.message}")
|
||||
report_error(e, message: "Error refreshing heartbeat import run #{run&.id}")
|
||||
run
|
||||
end
|
||||
|
||||
|
|
@ -175,7 +179,7 @@ class HeartbeatImportRunner
|
|||
rescue => e
|
||||
run = HeartbeatImportRun.includes(:user).find_by(id: import_run_id)
|
||||
fail_run!(run, message: e.message) if run && !run.terminal?
|
||||
Sentry.capture_exception(e) # Track unexpected errors
|
||||
report_error(e, message: "HeartbeatImportDumpJob failed") # Track unexpected errors
|
||||
ensure
|
||||
FileUtils.rm_f(file_path) if file_path.present?
|
||||
ActiveRecord::Base.connection_pool.release_connection
|
||||
|
|
@ -371,7 +375,7 @@ class HeartbeatImportRunner
|
|||
recipient_email:
|
||||
).deliver_later
|
||||
rescue => e
|
||||
Rails.logger.error("Failed to send heartbeat import completion email for run #{run.id}: #{e.message}")
|
||||
report_error(e, message: "Failed to send heartbeat import completion email for run #{run.id}")
|
||||
end
|
||||
|
||||
def self.send_failure_email(run)
|
||||
|
|
@ -386,7 +390,7 @@ class HeartbeatImportRunner
|
|||
recipient_email:
|
||||
).deliver_later
|
||||
rescue => e
|
||||
Rails.logger.error("Failed to send heartbeat import failure email for run #{run.id}: #{e.message}")
|
||||
report_error(e, message: "Failed to send heartbeat import failure email for run #{run.id}")
|
||||
end
|
||||
|
||||
def self.send_import_email?(run)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
class PosthogService
|
||||
class << self
|
||||
include ErrorReporting
|
||||
def capture(user_or_id, event, properties = {})
|
||||
return unless $posthog
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ class PosthogService
|
|||
properties: properties
|
||||
)
|
||||
rescue => e
|
||||
Rails.logger.error "PostHog capture error: #{e.message}"
|
||||
report_error(e, message: "PostHog capture error")
|
||||
end
|
||||
|
||||
def identify(user, properties = {})
|
||||
|
|
@ -29,7 +30,7 @@ class PosthogService
|
|||
}.merge(properties)
|
||||
)
|
||||
rescue => e
|
||||
Rails.logger.error "PostHog identify error: #{e.message}"
|
||||
report_error(e, message: "PostHog identify error")
|
||||
end
|
||||
|
||||
def capture_once_per_day(user, event, properties = {})
|
||||
|
|
@ -41,7 +42,7 @@ class PosthogService
|
|||
capture(user, event, properties)
|
||||
Rails.cache.write(cache_key, true, expires_at: Date.current.end_of_day + 1.hour)
|
||||
rescue => e
|
||||
Rails.logger.error "PostHog capture_once_per_day error: #{e.message}"
|
||||
report_error(e, message: "PostHog capture_once_per_day error")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module RepoHost
|
||||
class BaseService
|
||||
class BaseService < ApplicationService
|
||||
def initialize(user, repo_url)
|
||||
@user = user
|
||||
@repo_url = repo_url
|
||||
|
|
@ -47,7 +47,7 @@ module RepoHost
|
|||
Rails.logger.warn "[#{self.class.name}] Repository #{owner}/#{repo} not found (404)"
|
||||
nil
|
||||
else
|
||||
Rails.logger.error "[#{self.class.name}] API error. Status: #{response.status}"
|
||||
report_message("[#{self.class.name}] API error. Status: #{response.status}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ module RepoHost
|
|||
0
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "[#{self.class.name}] Error fetching commit count for #{owner}/#{repo}: #{e.message}"
|
||||
report_error(e, message: "[#{self.class.name}] Error fetching commit count for #{owner}/#{repo}")
|
||||
0
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ if ENV["POSTHOG_API_KEY"].present?
|
|||
$posthog = PostHog::Client.new({
|
||||
api_key: ENV["POSTHOG_API_KEY"],
|
||||
host: ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com"),
|
||||
on_error: proc { |status, msg| Rails.logger.error "PostHog error: #{status} - #{msg}" }
|
||||
on_error: proc { |status, msg| Sentry.capture_message("PostHog error: #{status} - #{msg}"); Rails.logger.error "PostHog error: #{status} - #{msg}" }
|
||||
})
|
||||
else
|
||||
$posthog = nil
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class Rack::Attack
|
|||
TOKENS = bypass_value.split(",").map(&:strip).reject(&:empty?).freeze
|
||||
Rails.logger.info "RACK_ATTACK_BYPASS loaded #{TOKENS.length} let me in tokens"
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error "RACK_ATTACK_BYPASS failed to read, you fucked it up #{e.message} raw: #{ENV['RACK_ATTACK_BYPASS'].inspect}"
|
||||
TOKENS = [].freeze
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace :cache do
|
|||
puts "✓ #{job_class.name} completed"
|
||||
rescue => e
|
||||
puts "✗ #{job_class.name} failed: #{e.message}"
|
||||
Sentry.capture_exception(e)
|
||||
Rails.logger.error("Cache warmup failed for #{job_class.name}: #{e.class.name} #{e.message}")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
include ApplicationHelper
|
||||
include ErrorReporting
|
||||
|
||||
class TestWakatimeService
|
||||
def initialize(user: nil, specific_filters: [], allow_cache: true, limit: 10, start_date: nil, end_date: nil, scope: nil, boundary_aware: false)
|
||||
|
|
@ -123,7 +124,7 @@ class TestWakatimeService
|
|||
end
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("Error parsing user agent string: #{e.message}")
|
||||
report_error(e, message: "Error parsing user agent string")
|
||||
{ os: "", editor: "", err: "failed to parse user agent string" }
|
||||
end
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ class TestWakatimeService
|
|||
nil
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
Rails.logger.error("Error converting timestamp: #{e.message}")
|
||||
report_error(e, message: "Error converting timestamp")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
include ApplicationHelper
|
||||
include ErrorReporting
|
||||
|
||||
class WakatimeService
|
||||
def initialize(user: nil, specific_filters: [], allow_cache: true, limit: 10, start_date: nil, end_date: nil, scope: nil)
|
||||
|
|
@ -109,7 +110,7 @@ class WakatimeService
|
|||
end
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("Error parsing user agent string: #{e.message}")
|
||||
report_error(e, message: "Error parsing user agent string")
|
||||
{ os: "", editor: "", err: "failed to parse user agent string" }
|
||||
end
|
||||
|
||||
|
|
@ -151,7 +152,7 @@ class WakatimeService
|
|||
nil
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
Rails.logger.error("Error converting timestamp: #{e.message}")
|
||||
report_error(e, message: "Error converting timestamp")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -178,8 +178,6 @@ class HeartbeatImportRunnerTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test "refreshable_remote_run? stops once a remote import is downloading or importing" do
|
||||
Flipper.enable_actor(:imports, User.first)
|
||||
|
||||
%i[downloading_dump importing].each do |state|
|
||||
user = User.create!(timezone: "UTC")
|
||||
Flipper.enable_actor(:imports, user)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue