dristi-client 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7066db6c24c6d8446f9e34a39f880db8270898a8fd8af5e8ddae68618962e2af
4
+ data.tar.gz: ebe3f6ff03b3d2cc44329584a27ae536d107ca3889537546458182210f0db09d
5
+ SHA512:
6
+ metadata.gz: db87e66caa842fe6af4ee74281f33292d775c63cf1a494d34f585bedf3cdeb3cc3a4c89bb633985ec8d53556cdfa3e9c77697bc93c34f28e718647ad6a0e6c69
7
+ data.tar.gz: 0eab437d7047428a6466c18506e9fcfa1b80dd2ab0000814bafd084cdea01282c9645254b469bd3468bbc79c04b02e16e37a68bcb7b5332f94f34857be9c74d4
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.2] - 2025-01-23
9
+
10
+ ### Added
11
+ - Capture HTTP referer in error context
12
+ - Include referer path for easier analysis
13
+
14
+ ### Fixed
15
+ - Middleware position to properly capture exceptions
16
+
17
+ ## [0.1.1] - 2025-01-23
18
+
19
+ ### Added
20
+ - Initial release
21
+ - Rack middleware for automatic exception capture
22
+ - Manual error reporting with `DristiClient.capture`
23
+ - Sensitive parameter filtering
24
+ - Retry logic with exponential backoff
25
+ - Fallback to local file when API is unavailable
26
+ - Rails integration via Railtie
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dristi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Dristi Client
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/dristi-client.svg)](https://rubygems.org/gems/dristi-client)
4
+
5
+ Ruby client for [Dristi](https://dristiapp.com) error tracking service.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby >= 3.0.0
10
+ - Rails (optional, for automatic middleware integration)
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem "dristi-client"
18
+ ```
19
+
20
+ Then execute:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ Create an initializer at `config/initializers/dristi_client.rb`:
29
+
30
+ ```ruby
31
+ DristiClient.configure do |config|
32
+ config.api_token = ENV["DRISTI_API_TOKEN"]
33
+
34
+ # Optional: disable in specific environments
35
+ config.enabled = !Rails.env.test?
36
+
37
+ # Optional: ignore specific exception classes
38
+ config.ignored_exceptions = [ActiveRecord::RecordNotFound]
39
+ end
40
+ ```
41
+
42
+ ### Configuration Options
43
+
44
+ | Option | Type | Default | Description |
45
+ |--------|------|---------|-------------|
46
+ | `api_token` | String | `nil` | Required. API token for authentication |
47
+ | `enabled` | Boolean | `true` | Enable/disable error reporting |
48
+ | `ignored_exceptions` | Array | `[]` | Exception classes to skip |
49
+
50
+ ## Usage
51
+
52
+ ### Automatic Error Capture
53
+
54
+ The gem automatically captures unhandled exceptions via Rack middleware. No additional code is needed for Rails applications.
55
+
56
+ The middleware captures the following context automatically:
57
+
58
+ - `request_method` - HTTP method (GET, POST, etc.)
59
+ - `request_path` - Request path
60
+ - `request_params` - Request parameters (sensitive params filtered)
61
+ - `query_string` - Query string
62
+ - `user_agent` - Browser/client user agent
63
+ - `remote_ip` - Client IP address
64
+ - `request_id` - Rails request ID
65
+ - `referer` - HTTP referer URL (if present)
66
+ - `referer_path` - Path portion of the referer URL
67
+
68
+ ### Manual Error Reporting
69
+
70
+ You can manually report errors with additional context:
71
+
72
+ ```ruby
73
+ begin
74
+ # risky code
75
+ rescue => e
76
+ DristiClient.capture(e, {
77
+ user_id: current_user.id,
78
+ action: "checkout",
79
+ custom_data: { cart_items: cart.items.count }
80
+ })
81
+ raise
82
+ end
83
+ ```
84
+
85
+ ### Custom Context
86
+
87
+ Add any relevant context to help with debugging:
88
+
89
+ ```ruby
90
+ DristiClient.capture(exception, {
91
+ user_id: current_user&.id,
92
+ user_email: current_user&.email,
93
+ feature_flags: FeatureFlags.active,
94
+ request_id: request.request_id
95
+ })
96
+ ```
97
+
98
+ ## Error Payload
99
+
100
+ Errors are reported with the following structure:
101
+
102
+ ```json
103
+ {
104
+ "exception_class": "NoMethodError",
105
+ "message": "undefined method 'foo' for nil:NilClass",
106
+ "backtrace": ["app/models/user.rb:42:in `process'", "..."],
107
+ "context": { "user_id": 123 },
108
+ "environment": {
109
+ "ruby_version": "3.3.0",
110
+ "rails_version": "8.1.2",
111
+ "rails_env": "production",
112
+ "hostname": "web-1"
113
+ },
114
+ "occurred_at": "2025-01-21T12:00:00Z"
115
+ }
116
+ ```
117
+
118
+ ## Retry & Fallback Behavior
119
+
120
+ - Errors are sent in a background thread to avoid blocking requests
121
+ - Failed requests retry up to 3 times with exponential backoff (1s, 2s, 4s)
122
+ - If all retries fail, errors are written to `tmp/dristi_fallback.jsonl`
123
+
124
+ ## Contributing
125
+
126
+ Bug reports and pull requests are welcome on GitHub at https://github.com/logicalgroove/dristi-client.
127
+
128
+ ## License
129
+
130
+ MIT License. See [LICENSE.txt](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/dristi_client/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "dristi-client"
7
+ spec.version = DristiClient::VERSION
8
+ spec.authors = ["Aleksander Lopez Yazikov"]
9
+ spec.email = ["webodessa@gmail.com"]
10
+
11
+ spec.summary = "Ruby client for Dristi error tracking"
12
+ spec.description = "A lightweight Ruby gem for reporting errors to Dristi error tracking service"
13
+ spec.homepage = "https://github.com/logicalgroove/dristi-client"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/logicalgroove/dristi-client"
19
+ spec.metadata["changelog_uri"] = "https://github.com/logicalgroove/dristi-client"
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
25
+ end
26
+ end
27
+
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "rake", "~> 13.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency "webmock", "~> 3.0"
33
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "socket"
6
+ require "uri"
7
+
8
+ module DristiClient
9
+ class Client
10
+ FALLBACK_FILE = "tmp/dristi_fallback.jsonl"
11
+
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+ end
15
+
16
+ def capture(exception, context = {})
17
+ return unless @configuration.enabled
18
+ return unless @configuration.valid?
19
+ return if ignored?(exception)
20
+
21
+ payload = build_payload(exception, context)
22
+
23
+ Thread.new do
24
+ post_with_retry(payload)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def ignored?(exception)
31
+ @configuration.ignored_exceptions.any? do |klass|
32
+ exception.is_a?(klass)
33
+ end
34
+ end
35
+
36
+ def build_payload(exception, context)
37
+ {
38
+ exception_class: exception.class.name,
39
+ message: exception.message,
40
+ backtrace: exception.backtrace || [],
41
+ context: context,
42
+ environment: gather_environment,
43
+ occurred_at: Time.now.utc.iso8601
44
+ }
45
+ end
46
+
47
+ def gather_environment
48
+ env = {
49
+ ruby_version: RUBY_VERSION,
50
+ hostname: Socket.gethostname
51
+ }
52
+
53
+ if defined?(Rails)
54
+ env[:rails_version] = Rails::VERSION::STRING
55
+ env[:rails_env] = Rails.env
56
+ end
57
+
58
+ env
59
+ end
60
+
61
+ def post_with_retry(payload, retries: 3)
62
+ uri = URI.parse(@configuration.endpoint)
63
+ attempt = 0
64
+
65
+ begin
66
+ attempt += 1
67
+ response = make_request(uri, payload)
68
+
69
+ unless response.is_a?(Net::HTTPSuccess)
70
+ raise "HTTP #{response.code}: #{response.body}"
71
+ end
72
+ rescue StandardError => e
73
+ if attempt < retries
74
+ sleep(2 ** (attempt - 1))
75
+ retry
76
+ else
77
+ write_to_fallback(payload, e.message)
78
+ end
79
+ end
80
+ end
81
+
82
+ def make_request(uri, payload)
83
+ http = Net::HTTP.new(uri.host, uri.port)
84
+ http.use_ssl = uri.scheme == "https"
85
+ http.open_timeout = 5
86
+ http.read_timeout = 10
87
+
88
+ request = Net::HTTP::Post.new(uri.request_uri)
89
+ request["Content-Type"] = "application/json"
90
+ request["X-API-Token"] = @configuration.api_token
91
+ request.body = JSON.generate(payload)
92
+
93
+ http.request(request)
94
+ end
95
+
96
+ def write_to_fallback(payload, error_message)
97
+ fallback_data = payload.merge(fallback_error: error_message)
98
+
99
+ if defined?(Rails)
100
+ path = Rails.root.join(FALLBACK_FILE)
101
+ else
102
+ path = FALLBACK_FILE
103
+ end
104
+
105
+ FileUtils.mkdir_p(File.dirname(path))
106
+ File.open(path, "a") do |f|
107
+ f.puts(JSON.generate(fallback_data))
108
+ end
109
+ rescue StandardError
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DristiClient
4
+ class Configuration
5
+ ENDPOINT = "https://dristiapp.com/api/errors"
6
+
7
+ attr_accessor :api_token, :enabled, :ignored_exceptions
8
+ attr_reader :endpoint
9
+
10
+ def initialize
11
+ @api_token = nil
12
+ @endpoint = ENDPOINT
13
+ @enabled = true
14
+ @ignored_exceptions = []
15
+ end
16
+
17
+ def valid?
18
+ !api_token.nil? && !api_token.empty?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DristiClient
4
+ class Middleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @app.call(env)
11
+ rescue Exception => e
12
+ context = extract_context(env)
13
+ DristiClient.client.capture(e, context)
14
+ raise
15
+ end
16
+
17
+ private
18
+
19
+ def extract_context(env)
20
+ context = {
21
+ request_method: env["REQUEST_METHOD"],
22
+ request_path: env["PATH_INFO"],
23
+ request_params: extract_params(env),
24
+ query_string: env["QUERY_STRING"],
25
+ user_agent: env["HTTP_USER_AGENT"],
26
+ remote_ip: env["HTTP_X_FORWARDED_FOR"] || env["REMOTE_ADDR"],
27
+ request_id: env["HTTP_X_REQUEST_ID"] || env["action_dispatch.request_id"]
28
+ }
29
+
30
+ if env["HTTP_REFERER"] && !env["HTTP_REFERER"].empty?
31
+ context[:referer] = env["HTTP_REFERER"]
32
+ context[:referer_path] = URI.parse(env["HTTP_REFERER"]).path rescue nil
33
+ end
34
+
35
+ context
36
+ end
37
+
38
+ def extract_params(env)
39
+ params = {}
40
+
41
+ if env["rack.request.form_hash"]
42
+ params.merge!(env["rack.request.form_hash"])
43
+ end
44
+
45
+ if env["action_dispatch.request.parameters"]
46
+ params.merge!(env["action_dispatch.request.parameters"])
47
+ end
48
+
49
+ filter_sensitive_params(params)
50
+ end
51
+
52
+ def filter_sensitive_params(params)
53
+ sensitive_keys = %w[password password_confirmation token api_key secret]
54
+
55
+ params.transform_values.with_index do |value, _|
56
+ key = params.keys[params.values.index(value)]
57
+ if sensitive_keys.any? { |k| key.to_s.downcase.include?(k) }
58
+ "[FILTERED]"
59
+ elsif value.is_a?(Hash)
60
+ filter_sensitive_params(value)
61
+ else
62
+ value
63
+ end
64
+ end
65
+ rescue StandardError
66
+ params
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DristiClient
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "dristi_client.insert_middleware" do |app|
6
+ app.config.middleware.insert_after(ActionDispatch::DebugExceptions, DristiClient::Middleware)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DristiClient
4
+ VERSION = "0.1.2"
5
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dristi_client/version"
4
+ require_relative "dristi_client/configuration"
5
+ require_relative "dristi_client/client"
6
+ require_relative "dristi_client/middleware"
7
+
8
+ module DristiClient
9
+ class << self
10
+ def configure
11
+ yield(configuration) if block_given?
12
+ end
13
+
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def client
19
+ @client ||= Client.new(configuration)
20
+ end
21
+
22
+ def capture(exception, context = {})
23
+ client.capture(exception, context)
24
+ end
25
+
26
+ def reset!
27
+ @configuration = nil
28
+ @client = nil
29
+ end
30
+ end
31
+ end
32
+
33
+ require_relative "dristi_client/railtie" if defined?(Rails::Railtie)
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dristi-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Aleksander Lopez Yazikov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: A lightweight Ruby gem for reporting errors to Dristi error tracking
56
+ service
57
+ email:
58
+ - webodessa@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - dristi-client.gemspec
68
+ - lib/dristi_client.rb
69
+ - lib/dristi_client/client.rb
70
+ - lib/dristi_client/configuration.rb
71
+ - lib/dristi_client/middleware.rb
72
+ - lib/dristi_client/railtie.rb
73
+ - lib/dristi_client/version.rb
74
+ homepage: https://github.com/logicalgroove/dristi-client
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ homepage_uri: https://github.com/logicalgroove/dristi-client
79
+ source_code_uri: https://github.com/logicalgroove/dristi-client
80
+ changelog_uri: https://github.com/logicalgroove/dristi-client
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.5.22
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Ruby client for Dristi error tracking
100
+ test_files: []