observable 0.1.0 → 0.1.4

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.
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Observable
4
+ class StructuredError < StandardError
5
+ attr_reader :context, :type
6
+
7
+ def initialize(message, type: self.class.name, context: {})
8
+ @context = context.to_h
9
+ @type = (type || self.class.name).to_s
10
+ super(message)
11
+ end
12
+
13
+ def to_h
14
+ {message: message}.merge(@context)
15
+ end
16
+
17
+ def inspect
18
+ "#<#{self.class.name}: #{message}, type=#{type}, context=#{@context}>"
19
+ end
20
+
21
+ def pretty_print
22
+ "#<#{self.class.name}: message: #{message}, type: #{type}, context: #{@context}>"
23
+ end
24
+
25
+ def self.from_error(error)
26
+ custom_converter_result = try_custom_converter(error)
27
+ if custom_converter_result
28
+ new(
29
+ custom_converter_result[:message],
30
+ type: custom_converter_result[:type],
31
+ context: custom_converter_result[:context]
32
+ )
33
+ else
34
+ new(
35
+ safe_message(error),
36
+ type: safe_type(error),
37
+ context: safe_context(error)
38
+ )
39
+ end
40
+ end
41
+
42
+ def self.safe_message(error)
43
+ if error.respond_to?(:message)
44
+ error.message.to_s
45
+ else
46
+ ""
47
+ end
48
+ rescue
49
+ ""
50
+ end
51
+
52
+ def self.safe_type(error)
53
+ return "NilClass" if error.nil?
54
+
55
+ if error.respond_to?(:type)
56
+ type_value = error.type
57
+ return type_value.to_s unless type_value.nil? || type_value.to_s.empty?
58
+ end
59
+
60
+ if error.respond_to?(:name)
61
+ name_value = error.name
62
+ return name_value.to_s unless name_value.nil? || name_value.to_s.empty?
63
+ end
64
+
65
+ error.class.name
66
+ rescue
67
+ error.class.name
68
+ end
69
+
70
+ def self.safe_context(error)
71
+ if error.nil?
72
+ {}
73
+ elsif error.respond_to?(:to_h)
74
+ error.to_h
75
+ elsif error.respond_to?(:context)
76
+ error.context
77
+ else
78
+ {}
79
+ end
80
+ rescue
81
+ {}
82
+ end
83
+
84
+ def self.try_custom_converter(error)
85
+ return nil if error.nil?
86
+
87
+ converters = Configuration.config.custom_error_converters
88
+ return nil if converters.empty?
89
+
90
+ converter = converters[error.class.name]
91
+ return nil unless converter
92
+
93
+ result = begin
94
+ converter.call(error)
95
+ rescue
96
+ nil
97
+ end
98
+
99
+ if result&.is_a?(Hash) &&
100
+ result.key?(:message) &&
101
+ result.key?(:type) &&
102
+ result.key?(:context)
103
+ result
104
+ end
105
+ end
106
+
107
+ private_class_method :safe_message, :safe_type, :safe_context, :try_custom_converter
108
+ end
109
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "persistence/trace_repo"
2
+ require_relative "persistence/span_repo"
3
+
4
+ module Observable
5
+ module TracingTestHelper
6
+ def traces
7
+ Observable::Persistence::TraceRepo.new(spans: finished_spans)
8
+ end
9
+
10
+ def spans
11
+ Observable::Persistence::SpanRepo.new(spans: finished_spans)
12
+ end
13
+
14
+ def setup_observable_data!
15
+ @open_telemetry_exporter = setup_opentelemetry_for_tests
16
+ end
17
+
18
+ def teardown_observable_data!
19
+ reset_observable_data!
20
+ @open_telemetry_exporter = nil
21
+ end
22
+
23
+ def reset_observable_data!
24
+ raise NoOpenTelemetryExporter if @open_telemetry_exporter.nil?
25
+
26
+ @open_telemetry_exporter.reset
27
+ end
28
+
29
+ private
30
+
31
+ def finished_spans
32
+ raise NoOpenTelemetryExporter if @open_telemetry_exporter.nil?
33
+
34
+ @open_telemetry_exporter.finished_spans
35
+ end
36
+
37
+ def setup_opentelemetry_for_tests
38
+ require "opentelemetry/sdk"
39
+
40
+ exporter = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
41
+ span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)
42
+
43
+ # Configure the tracer provider with our span processor
44
+ tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
45
+ tracer_provider.add_span_processor(span_processor)
46
+ OpenTelemetry.tracer_provider = tracer_provider
47
+
48
+ exporter
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Observable
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/observable.rb CHANGED
@@ -1,8 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "observable/version"
4
+ require_relative "observable/configuration"
5
+ require_relative "observable/instrumenter"
6
+ require_relative "observable/structured_error"
7
+ require_relative "observable/persistence/span"
8
+ require_relative "observable/persistence/span_repo"
9
+ require_relative "observable/persistence/trace"
10
+ require_relative "observable/persistence/trace_repo"
4
11
 
5
12
  module Observable
6
13
  class Error < StandardError; end
7
- # Your code goes here...
14
+
15
+ class NoOpenTelemetryExporter < Error
16
+ def message
17
+ "No OpenTelemetry exporter set up. Call `Observable::TracingTestHelper#use_in_memory_otel_exporter!` to set up the exporter."
18
+ end
19
+ end
20
+
21
+ class NotFound < Error; end
22
+
23
+ def self.instrumenter(config: nil)
24
+ Instrumenter.new(config: config)
25
+ end
26
+
27
+ def self.configure
28
+ yield Configuration.config
29
+ end
8
30
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/observable/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "observable"
7
+ spec.version = Observable::VERSION
8
+ spec.authors = ["John Gallagher"]
9
+ spec.email = ["john@synapticmishap.co.uk"]
10
+
11
+ spec.summary = "OpenTelemetry instrumentation library for Ruby methods"
12
+ spec.description = "A Ruby gem that provides automated OpenTelemetry instrumentation for method calls with configurable serialization, PII filtering, and argument tracking"
13
+ spec.homepage = "https://github.com/JoyfulProgramming/observable"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/JoyfulProgramming/observable"
21
+ spec.metadata["changelog_uri"] = "https://github.com/JoyfulProgramming/observable/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Runtime dependencies
35
+ spec.add_dependency "opentelemetry-sdk", "~> 1.5"
36
+ spec.add_dependency "dry-configurable", ">= 0.13.0", "< 2.0"
37
+ spec.add_dependency "dry-struct", "~> 1.4"
38
+
39
+ # Development dependencies
40
+ spec.add_development_dependency "m", "~> 1.6"
41
+ spec.add_development_dependency "bump", "~> 0.10"
42
+ spec.add_development_dependency "minitest", "~> 5.14"
43
+ spec.add_development_dependency "opentelemetry-exporter-otlp", "~> 0.30"
44
+ spec.add_development_dependency "rake", "~> 13.0"
45
+ spec.add_development_dependency "simplecov", "~> 0.22"
46
+ spec.add_development_dependency "standard", "~> 1.40"
47
+ spec.add_development_dependency "super_diff", "~> 0.9"
48
+ spec.add_development_dependency "debug", "~> 1.8"
49
+ end
data/pretty.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "pp"
2
+
3
+ data = {
4
+ users: [
5
+ {name: "Alice", email: "alice@example.com", roles: ["admin", "user"], active: true, created_at: Time.now},
6
+ {name: "Bob", email: "bob@example.com", roles: ["user"], active: false, created_at: Time.now},
7
+ {name: "Charlie", email: "charlie@example.com", roles: ["moderator", "user"], active: true, created_at: Time.now}
8
+ ],
9
+ settings: {theme: "dark", notifications: true, language: "en"}
10
+ }
11
+
12
+ puts data
metadata CHANGED
@@ -1,31 +1,220 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: observable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Gallagher
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-04-22 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Observe your app using Open Telemetry
10
+ date: 2025-10-04 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: opentelemetry-sdk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.5'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.5'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-configurable
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.13.0
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '2.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.13.0
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: dry-struct
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.4'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.4'
60
+ - !ruby/object:Gem::Dependency
61
+ name: m
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.6'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.6'
74
+ - !ruby/object:Gem::Dependency
75
+ name: bump
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.10'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.10'
88
+ - !ruby/object:Gem::Dependency
89
+ name: minitest
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '5.14'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '5.14'
102
+ - !ruby/object:Gem::Dependency
103
+ name: opentelemetry-exporter-otlp
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.30'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '0.30'
116
+ - !ruby/object:Gem::Dependency
117
+ name: rake
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '13.0'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '13.0'
130
+ - !ruby/object:Gem::Dependency
131
+ name: simplecov
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '0.22'
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '0.22'
144
+ - !ruby/object:Gem::Dependency
145
+ name: standard
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '1.40'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '1.40'
158
+ - !ruby/object:Gem::Dependency
159
+ name: super_diff
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: '0.9'
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '0.9'
172
+ - !ruby/object:Gem::Dependency
173
+ name: debug
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '1.8'
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '1.8'
186
+ description: A Ruby gem that provides automated OpenTelemetry instrumentation for
187
+ method calls with configurable serialization, PII filtering, and argument tracking
14
188
  email:
15
189
  - john@synapticmishap.co.uk
16
190
  executables: []
17
191
  extensions: []
18
192
  extra_rdoc_files: []
19
193
  files:
194
+ - ".claude/settings.local.json"
195
+ - ".ruby-version"
20
196
  - ".standard.yml"
21
197
  - CHANGELOG.md
198
+ - CLAUDE.md
22
199
  - CODE_OF_CONDUCT.md
23
200
  - Gemfile
201
+ - Gemfile.lock
24
202
  - LICENSE.txt
25
203
  - README.md
26
204
  - Rakefile
205
+ - example.rb
27
206
  - lib/observable.rb
207
+ - lib/observable/configuration.rb
208
+ - lib/observable/instrumenter.rb
209
+ - lib/observable/persistence/span.rb
210
+ - lib/observable/persistence/span_repo.rb
211
+ - lib/observable/persistence/trace.rb
212
+ - lib/observable/persistence/trace_repo.rb
213
+ - lib/observable/structured_error.rb
214
+ - lib/observable/tracing_test_helper.rb
28
215
  - lib/observable/version.rb
216
+ - observable.gemspec
217
+ - pretty.rb
29
218
  - sig/observable.rbs
30
219
  homepage: https://github.com/JoyfulProgramming/observable
31
220
  licenses:
@@ -35,7 +224,6 @@ metadata:
35
224
  homepage_uri: https://github.com/JoyfulProgramming/observable
36
225
  source_code_uri: https://github.com/JoyfulProgramming/observable
37
226
  changelog_uri: https://github.com/JoyfulProgramming/observable/CHANGELOG.md
38
- post_install_message:
39
227
  rdoc_options: []
40
228
  require_paths:
41
229
  - lib
@@ -43,15 +231,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
43
231
  requirements:
44
232
  - - ">="
45
233
  - !ruby/object:Gem::Version
46
- version: 2.6.0
234
+ version: 3.2.0
47
235
  required_rubygems_version: !ruby/object:Gem::Requirement
48
236
  requirements:
49
237
  - - ">="
50
238
  - !ruby/object:Gem::Version
51
239
  version: '0'
52
240
  requirements: []
53
- rubygems_version: 3.3.7
54
- signing_key:
241
+ rubygems_version: 3.6.2
55
242
  specification_version: 4
56
- summary: Observe your app using Open Telemetry
243
+ summary: OpenTelemetry instrumentation library for Ruby methods
57
244
  test_files: []