Skip to content

Commit

Permalink
feat(CE): sentry destinator connector
Browse files Browse the repository at this point in the history
  • Loading branch information
Raushan Kumar Raman committed Dec 13, 2024
1 parent 05bb9fa commit 8dd0440
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 0 deletions.
2 changes: 2 additions & 0 deletions integrations/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ gem "aws-sdk-sagemakerruntime"

gem "google-cloud-ai_platform-v1"

gem 'sentry-ruby'

group :development, :test do
gem "simplecov", require: false
gem "simplecov_json_formatter", require: false
Expand Down
4 changes: 4 additions & 0 deletions integrations/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ GEM
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sentry-ruby (5.22.0)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
sequel (5.80.0)
bigdecimal
signet (0.19.0)
Expand Down Expand Up @@ -429,6 +432,7 @@ DEPENDENCIES
ruby-oci8 (~> 2.2.12)
ruby-odbc!
rubyzip
sentry-ruby
sequel
simplecov
simplecov_json_formatter
Expand Down
2 changes: 2 additions & 0 deletions integrations/lib/multiwoven/integrations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
require "aws-sdk-sagemakerruntime"
require "google/cloud/ai_platform/v1"
require "grpc"
require "sentry-ruby"

# Service
require_relative "integrations/config"
Expand Down Expand Up @@ -89,6 +90,7 @@
require_relative "integrations/destination/databricks_lakehouse/client"
require_relative "integrations/destination/oracle_db/client"
require_relative "integrations/destination/microsoft_excel/client"
require_relative "integrations/destination/sentry/client"

module Multiwoven
module Integrations
Expand Down
1 change: 1 addition & 0 deletions integrations/lib/multiwoven/integrations/core/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module Constants

GOOGLE_VERTEX_ENDPOINT_SERVICE_URL = "%<region>s-aiplatform.googleapis.com"
GOOGLE_VERTEX_MODEL_NAME = "projects/%<project_id>s/locations/%<region>s/endpoints/%<endpoint_id>s"
SENTRY_API = "https://%<public_key>:<secret_key>@%<host>/<project_id>"

# HTTP
HTTP_GET = "GET"
Expand Down
110 changes: 110 additions & 0 deletions integrations/lib/multiwoven/integrations/destination/sentry/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# frozen_string_literal: true

require "sentry-ruby"

module Multiwoven::Integrations::Destination
module Sentry
include Multiwoven::Integrations::Core
class Client < DestinationConnector
attr_reader :dsn_link

def initialize(connection_config)
@dsn_link = connection_config[:dsn]
configure_sentry(connection_config)
end

def check_connection
response = Multiwoven::Integrations::Core::HttpClient.request(
host,
"POST",
headers: {
"Content-Type" => "application/json",
"X-Sentry-Auth" => "Sentry sentry_version=7, sentry_key=#{public_key}"
}
)
if response[:code].to_i == 200
success_status
else
failure_status
end
rescue StandardError => e
handle_exception(e,
context: "SENTRY::CONNECTION::FAILURE",
type: "error")
failure_status
end

def write(sync_config, records)
@sync_config = sync_config
process_records(records)
rescue StandardError => e
handle_exception(e, {
context: "SENTRY:WRITE:EXCEPTION",
type: "error",
sync_id: @sync_config.sync_id,
sync_run_id: @sync_config.sync_run_id
})
end

private

def process_records(sync_config, records)
write_success = 0
write_failure = 0
records.each do |record|
::Sentry.capture_exception(record)
write_success += 1
rescue StandardError => e
write_failure += 1
handle_exception(e, {
context: "SENTRY:WRITE:EXCEPTION",
type: "error",
sync_id: sync_config.sync_id,
sync_run_id: sync_config.sync_run_id
})
end
end

def configure_sentry(connection_config)
::Sentry.init do |config|
config.dsn = connection_config[:dsn]
config.environment = connection_config[:environment]
config.release = connection_config[:release] || "default-release"
config.debug = connection_config[:debug] || true
config.traces_sample_rate = connection_config[:traces_sample_rate] || 0.5
config.breadcrumbs_logger = %i[active_support_logger http_logger]
config.background_worker_threads = connection_config[:worker_threads] || 5

config.excluded_exceptions = connection_config[:excluded_exceptions] || []
config.before_send = lambda do |_even, hint|
if hint[:exception] && config.excluded_exceptions.any? { |e| hint[:exception].is_a?(e) }
nil
else
event
end
end
end
end

def dsn
@dsn ||= ::Sentry::DSN.new(@dsn_link)
end

def host
@host ||= dsn.host
end

def public_key
@public_key ||= dsn.public_key
end

def success_status
ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
end

def failure_status(_exception = nil)
ConnectionStatus.new(status: ConnectionStatusType["failed"]).to_multiwoven_message
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"data": {
"name": "Sentry",
"title": "Sentry",
"connector_type": "destination",
"category": "Productivity Tools",
"documentation_url": "https://docs.squared.ai/guides/data-integration/destination/sentry",
"github_issue_label": "destination-sentry",
"icon": "icon.svg",
"license": "MIT",
"release_stage": "alpha",
"support_level": "community",
"tags": ["language:ruby", "multiwoven"]
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"documentationUrl": "https://docs.squared.ai/guides/data-integration/destination/sentry",
"changelogUrl": "https://docs.squared.ai/guides/data-integration/destination/sentry#changelog",
"streamType": "dynamic",
"connectionSpecification": {
"type": "object",
"required": "[dsn environment]",
"properties": {
"dsn": {
"type": "string",
"description": "The Data Source Name (DSN) for your Sentry project. This is used to identify and authenticate with your Sentry instance."
},
"environment": {
"type": "string",
"description": "The environment name (e.g., production, staging, or development) to associate with the events sent to Sentry.",
"examples": "[production staging development]"
},
"release": {
"type": "string",
"description": "The version of the application associated with the events. This can help in tracking issues across deployments.",
"default": "default-release",
"examples": ["1.0.0", "2.3.4"]
},
"debug": {
"type": "boolean",
"description": "Enable or disable debug mode for the Sentry integration.",
"default": true
},
"traceSampleRate": {
"type": "number",
"description": "A decimal value between 0 and 1 indicating the percentage of transactions to be sampled for performance monitoring.",
"minimum": 0,
"maximum": 1,
"default": 0.5
},
"breadcrumbsLogger": {
"type": "array",
"description": "The loggers to be used for breadcrumbs (e.g., ActiveSupportLogger, HttpLogger).",
"items": {
"type": "string",
"enum": "[active_support_logger http_logger]"
},
"default": "[active_support_logger http_logger]"
},
"backgroundWorkerThreads": {
"type": "integer",
"description": "The number of background worker threads for processing.",
"default": 5
},
"excludedExceptions": {
"type": "array",
"description": "List of exceptions to be excluded from Sentry reporting.",
"items": {
"type": "string"
},
"default": []
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
125 changes: 125 additions & 0 deletions integrations/spec/multiwoven/integrations/sentry/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# frozen_string_literal: true

RSpec.describe Multiwoven::Integrations::Destination::Sentry::Client do
let(:connection_config) do
{
dsn: "https://598589589@p458458945894589.ingest.co/45894590549",
environment: "production"
}
end
let(:sync_config) { instance_double("Sync Config", sync_id: 1, sync_run_id: 2) }
let(:records) do
[
instance_double("valid_standard_error"),
instance_double("valid_argument_error"),
instance_double("valid_activerecord_error"),
instance_double("valid_record_not_found_error")
]
end

let(:sentry_client) { described_class.new(connection_config) }

describe "#check_connection" do
context "when successful connection" do
before do
allow(Multiwoven::Integrations::Core::HttpClient).to receive(:request).with(
"p458458945894589.ingest.co",
"POST",
headers: {
"Content-Type" => "application/json",
"X-Sentry-Auth" => "Sentry sentry_version=7, sentry_key=598589589"
}
).and_return({ code: 200 })
end

it "returns a successful connection" do
response = sentry_client.check_connection
expect(response).to be_a(Multiwoven::Integrations::Protocol::MultiwovenMessage)
expect(response.connection_status.status).to eq("succeeded")
end
end

context "when connection fails" do
before do
allow(Multiwoven::Integrations::Core::HttpClient).to receive(:request).with(
"p458458945894589.ingest.co",
"POST",
headers: {
"Content-Type" => "application/json",
"X-Sentry-Auth" => "Sentry sentry_version=7, sentry_key=598589589"
}
).and_return({ code: 404 })
end

it "returns a failure status" do
response = sentry_client.check_connection
expect(response).to be_a(Multiwoven::Integrations::Protocol::MultiwovenMessage)
expect(response.connection_status.status).to eq("failed")
end
end
end

describe "#write" do
context "when successful write" do
it "writes errors to Sentry and handles exceptions" do
expect(sentry_client).to receive(:process_records).with(records)
sentry_client.write(sync_config, records)
end
end

context "when failure write" do
before do
allow(sentry_client).to receive(:process_records).with(records).and_raise(StandardError)
end
it "raise error on process record" do
expect(sentry_client).to receive(:handle_exception).with(
StandardError, {
context: "SENTRY:WRITE:EXCEPTION",
type: "error",
sync_id: sync_config.sync_id,
sync_run_id: sync_config.sync_run_id
}
)
sentry_client.write(sync_config, records)
end
end
end

describe "#process_records" do
context "when process records success" do
before do
allow(::Sentry).to receive(:capture_exception)
end

it "captures sentry exception" do
records.each do |_record|
expect(::Sentry).to receive(:capture_exception)
end
sentry_client.send(:process_records, sync_config, records)
end
end

context "when process sync fails" do
let(:invalid_records) { [double("InvalidRecord")] }

before do
allow(::Sentry).to receive(:capture_exception).and_raise(StandardError)
end

it "handles exceptions raised during processing" do
invalid_records.each do
expect(sentry_client).to receive(:handle_exception).with(
StandardError, {
context: "SENTRY:WRITE:EXCEPTION",
type: "error",
sync_id: sync_config.sync_id,
sync_run_id: sync_config.sync_run_id
}
)

sentry_client.send(:process_records, sync_config, invalid_records)
end
end
end
end
end
2 changes: 2 additions & 0 deletions server/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ gem "jwt"
gem "kaminari"
gem "liquid"
gem "rack-cors"
gem 'sentry'
gem 'sentry-rails'

# AuthN & AuthZ
gem "devise"
Expand Down
5 changes: 5 additions & 0 deletions server/config/initializers/sentry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Sentry.init do |config|
# setup the enviroment
config.enviroment = ENV['RAILS_ENV']

end

0 comments on commit 8dd0440

Please sign in to comment.