trace_viz 0.0.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +15 -0
  4. data/.tool-versions +1 -0
  5. data/.vscode/launch.json +32 -0
  6. data/.vscode/settings.json +33 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +167 -0
  10. data/Rakefile +12 -0
  11. data/examples/example.rb +42 -0
  12. data/lib/trace_viz/adapters/base_adapter.rb +13 -0
  13. data/lib/trace_viz/adapters/trace_point/depth_manager.rb +34 -0
  14. data/lib/trace_viz/adapters/trace_point/event_handler.rb +36 -0
  15. data/lib/trace_viz/adapters/trace_point/trace_data.rb +89 -0
  16. data/lib/trace_viz/adapters/trace_point/trace_formatter.rb +95 -0
  17. data/lib/trace_viz/adapters/trace_point/trace_logger.rb +44 -0
  18. data/lib/trace_viz/adapters/trace_point_adapter.rb +25 -0
  19. data/lib/trace_viz/configuration.rb +61 -0
  20. data/lib/trace_viz/context/base_context.rb +13 -0
  21. data/lib/trace_viz/context/config_context.rb +34 -0
  22. data/lib/trace_viz/context/manager/context_map.rb +31 -0
  23. data/lib/trace_viz/context/manager/context_operations.rb +60 -0
  24. data/lib/trace_viz/context/manager/context_registry.rb +20 -0
  25. data/lib/trace_viz/context/manager/context_validation.rb +34 -0
  26. data/lib/trace_viz/context/manager.rb +37 -0
  27. data/lib/trace_viz/context/tracking/depth.rb +31 -0
  28. data/lib/trace_viz/context/tracking_context.rb +18 -0
  29. data/lib/trace_viz/context.rb +13 -0
  30. data/lib/trace_viz/core/tracer.rb +19 -0
  31. data/lib/trace_viz/core.rb +8 -0
  32. data/lib/trace_viz/errors.rb +8 -0
  33. data/lib/trace_viz/logger.rb +73 -0
  34. data/lib/trace_viz/utils/colorize.rb +35 -0
  35. data/lib/trace_viz/version.rb +5 -0
  36. data/lib/trace_viz.rb +13 -0
  37. data/sig/trace_viz.rbs +4 -0
  38. metadata +83 -0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/utils/colorize"
4
+
5
+ module TraceViz
6
+ module Adapters
7
+ module TracePoint
8
+ class TraceFormatter
9
+ def initialize(trace_data)
10
+ @trace_data = trace_data
11
+ @config = @trace_data.config
12
+ @logger = TraceViz.logger
13
+ end
14
+
15
+ def format_call
16
+ return "" if trace_data.exceeded_max_depth?
17
+
18
+ [
19
+ indent_if_enabled,
20
+ colorize(depth_if_enabled, :blue),
21
+ colorize(method_name_if_enabled, :light_green),
22
+ colorize(source_location_if_enabled, :dark_gray),
23
+ colorize(params_if_enabled, :yellow),
24
+ ].compact.join(" ")
25
+ end
26
+
27
+ def format_return
28
+ return "" if trace_data.exceeded_max_depth?
29
+
30
+ [
31
+ indent_if_enabled,
32
+ colorize(depth_if_enabled, :blue),
33
+ colorize(method_name_if_enabled, :light_green),
34
+ colorize(result_if_enabled, :cyan),
35
+ colorize(source_location_if_enabled, :dark_gray),
36
+ colorize(execution_time_if_enabled, :red),
37
+ ].compact.join(" ")
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :trace_data, :config, :logger
43
+
44
+ def indent_if_enabled
45
+ return unless config.show_indent && config.show_depth
46
+
47
+ " " * (config.tab_size * trace_data.depth)
48
+ end
49
+
50
+ def depth_if_enabled
51
+ return unless config.show_depth
52
+
53
+ "#depth:#{trace_data.depth}"
54
+ end
55
+
56
+ def method_name_if_enabled
57
+ return unless config.show_method_name
58
+
59
+ "#{trace_data.klass}##{trace_data.id}"
60
+ end
61
+
62
+ def source_location_if_enabled
63
+ return unless config.show_source_location
64
+
65
+ "at #{trace_data.path}:#{trace_data.line_number}"
66
+ end
67
+
68
+ def params_if_enabled
69
+ return unless config.show_params
70
+
71
+ param_values = trace_data.params.map do |var|
72
+ trace_data.trace_point.binding.local_variable_get(var).inspect
73
+ end.join(", ")
74
+ "(#{param_values})"
75
+ end
76
+
77
+ def result_if_enabled
78
+ return unless config.show_return_value
79
+
80
+ "#=> #{trace_data.result.inspect}"
81
+ end
82
+
83
+ def execution_time_if_enabled
84
+ return unless config.show_execution_time && trace_data.duration
85
+
86
+ "in #{trace_data.duration}ms"
87
+ end
88
+
89
+ def colorize(text, color_key)
90
+ Utils::Colorize.colorize(text, color_key)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Adapters
5
+ module TracePoint
6
+ class TraceLogger
7
+ def initialize(trace_data)
8
+ @trace_data = trace_data
9
+ @config = @trace_data.config
10
+ @formatter = TraceFormatter.new(@trace_data)
11
+ end
12
+
13
+ def log_trace
14
+ return unless should_log?
15
+
16
+ case trace_data.event
17
+ when :call
18
+ log_call
19
+ when :return
20
+ log_return
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :trace_data, :config, :formatter
27
+
28
+ def log_call
29
+ formatted_call = formatter.format_call
30
+ TraceViz.logger.start(formatted_call)
31
+ end
32
+
33
+ def log_return
34
+ formatted_return = formatter.format_return
35
+ TraceViz.logger.finish(formatted_return)
36
+ end
37
+
38
+ def should_log?
39
+ config.show_trace_events.include?(trace_data.event)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/logger"
4
+ require "trace_viz/adapters/base_adapter"
5
+ require "trace_viz/adapters/trace_point/trace_formatter"
6
+ require "trace_viz/adapters/trace_point/trace_logger"
7
+ require "trace_viz/adapters/trace_point/trace_data"
8
+ require "trace_viz/adapters/trace_point/event_handler"
9
+
10
+ module TraceViz
11
+ module Adapters
12
+ class TracePointAdapter < BaseAdapter
13
+ def trace(&block)
14
+ ::TracePoint.new(:call, :return) do |tp|
15
+ trace_data = TracePoint::TraceData.new(tp)
16
+
17
+ next if trace_data.internal_call?
18
+ next if trace_data.exceeded_max_depth?
19
+
20
+ TracePoint::EventHandler.new(trace_data).handle
21
+ end.enable(&block)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/logger"
4
+
5
+ module TraceViz
6
+ class Configuration
7
+ attr_accessor :logger,
8
+ :tab_size,
9
+ :show_indent,
10
+ :show_depth,
11
+ :max_display_depth,
12
+ :show_method_name,
13
+ :show_source_location,
14
+ :show_params,
15
+ :show_return_value,
16
+ :show_execution_time,
17
+ :show_trace_events
18
+
19
+ def initialize
20
+ @logger = Logger.new
21
+ @tab_size = 2
22
+ @show_indent = true
23
+ @show_depth = true
24
+ @max_display_depth = 3 # Recommended to keep this value between 3 and 5
25
+ @show_method_name = true
26
+ @show_source_location = false
27
+ @show_params = true
28
+ @show_return_value = true
29
+ @show_execution_time = true
30
+ @show_trace_events = [:call, :return]
31
+ end
32
+
33
+ def dup
34
+ copy = self.class.new
35
+ instance_variables.each do |var|
36
+ value = instance_variable_get(var)
37
+ copy_value = begin
38
+ value.dup
39
+ rescue TypeError
40
+ value
41
+ end
42
+ copy.instance_variable_set(var, copy_value)
43
+ end
44
+ copy
45
+ end
46
+ end
47
+
48
+ class << self
49
+ def configuration
50
+ @configuration ||= Configuration.new
51
+ end
52
+
53
+ def configure
54
+ yield(configuration) if block_given?
55
+ end
56
+
57
+ def logger
58
+ configuration.logger
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Context
5
+ class BaseContext
6
+ attr_reader :options
7
+
8
+ def initialize(**options)
9
+ @options = options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/context/base_context"
4
+
5
+ module TraceViz
6
+ module Context
7
+ class ConfigContext < BaseContext
8
+ attr_reader :configuration
9
+
10
+ def initialize(**options)
11
+ super
12
+
13
+ @configuration = apply_options(options)
14
+ end
15
+
16
+ def temp_configuration
17
+ @temp_configuration ||= TraceViz.configuration.dup
18
+ end
19
+
20
+ # Apply the overrides from options
21
+ def apply_options(options)
22
+ options.each do |key, value|
23
+ if temp_configuration.respond_to?("#{key}=")
24
+ temp_configuration.send("#{key}=", value)
25
+ else
26
+ warn("TraceViz: Unknown configuration option '#{key}'")
27
+ end
28
+ end
29
+
30
+ temp_configuration
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Context
5
+ class Manager
6
+ module ContextMap
7
+ def get_context(key)
8
+ validate_key!(key)
9
+ @context_map[key]
10
+ end
11
+
12
+ def clear
13
+ @context_map.clear
14
+ end
15
+
16
+ def empty?
17
+ @context_map.empty?
18
+ end
19
+
20
+ def all_contexts
21
+ @context_map.map do |key, context|
22
+ {
23
+ key: key,
24
+ context: context,
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/context/base_context"
4
+
5
+ module TraceViz
6
+ module Context
7
+ class Manager
8
+ module ContextOperations
9
+ def create_context(key = nil, **options)
10
+ if key
11
+ validate_key!(key)
12
+ ensure_unique_key!(key)
13
+ end
14
+
15
+ context_class = @registered_contexts[key] || BaseContext
16
+ context = context_class.new(**options)
17
+ @context_map[key] = context if key
18
+ context
19
+ end
20
+
21
+ def enter_context(key = nil, **options)
22
+ create_context(key, **options)
23
+ end
24
+
25
+ def enter_multiple_contexts(contexts_with_options = {})
26
+ unless contexts_with_options.is_a?(Hash)
27
+ raise ContextError, "Expected a Hash of contexts and options, got #{contexts_with_options.class}"
28
+ end
29
+
30
+ contexts_with_options.each do |key, options|
31
+ enter_context(key, **options)
32
+ end
33
+ end
34
+
35
+ def exit_context(key)
36
+ validate_key!(key)
37
+ context = @context_map.delete(key)
38
+ raise ContextError, "No context found with key '#{key}' to exit" unless context
39
+
40
+ context
41
+ end
42
+
43
+ def exit_multiple_contexts(*keys)
44
+ keys.each do |key|
45
+ validate_key!(key)
46
+ exit_context(key)
47
+ end
48
+ end
49
+
50
+ # Execute a block within the context(s) specified
51
+ def with_contexts(contexts_with_options = {})
52
+ enter_multiple_contexts(contexts_with_options)
53
+ yield
54
+ ensure
55
+ exit_multiple_contexts(*contexts_with_options.keys)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Context
5
+ class Manager
6
+ module ContextRegistry
7
+ def register_context_type(key, context_class)
8
+ validate_key!(key)
9
+ validate_context_class!(context_class)
10
+
11
+ if @registered_contexts.key?(key)
12
+ raise ContextError, "Context type for key '#{key}' is already registered"
13
+ end
14
+
15
+ @registered_contexts[key] = context_class
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Context
5
+ class Manager
6
+ module ContextValidation
7
+ private
8
+
9
+ # Validate that the key is either a Symbol or String
10
+ def validate_key!(key)
11
+ return unless key
12
+
13
+ unless key.is_a?(Symbol) || key.is_a?(String)
14
+ raise ContextError, "Key must be a Symbol or String, got #{key.class}"
15
+ end
16
+ end
17
+
18
+ # Ensure that the key is unique (no existing context with the same key)
19
+ def ensure_unique_key!(key)
20
+ if @context_map.key?(key)
21
+ raise ContextError, "Context with key '#{key}' already exists"
22
+ end
23
+ end
24
+
25
+ # Validate that the context_class inherits from BaseContext
26
+ def validate_context_class!(context_class)
27
+ unless context_class < BaseContext
28
+ raise ContextError, "Context class must inherit from BaseContext"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/context/manager/context_map"
4
+ require "trace_viz/context/manager/context_validation"
5
+ require "trace_viz/context/manager/context_registry"
6
+ require "trace_viz/context/manager/context_operations"
7
+ require "trace_viz/context/config_context"
8
+ require "trace_viz/context/tracking_context"
9
+
10
+ module TraceViz
11
+ module Context
12
+ class Manager
13
+ class << self
14
+ include ContextMap
15
+ include ContextValidation
16
+ include ContextRegistry
17
+ include ContextOperations
18
+
19
+ # Initialize class instance variables
20
+ def initialize_manager
21
+ @context_map = {}
22
+ @registered_contexts = {}
23
+
24
+ register_default_contexts
25
+ end
26
+
27
+ def register_default_contexts
28
+ Manager.register_context_type(:config, ConfigContext)
29
+ Manager.register_context_type(:tracking, TrackingContext)
30
+ end
31
+ end
32
+
33
+ # Ensure initialization upon loading
34
+ initialize_manager
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Context
5
+ module Tracking
6
+ class Depth
7
+ attr_reader :current
8
+
9
+ def initialize(current = 0)
10
+ @current = current
11
+ end
12
+
13
+ def increment
14
+ @current += 1
15
+ end
16
+
17
+ def decrement
18
+ @current -= 1 if @current.positive?
19
+ end
20
+
21
+ def reset
22
+ @current = 0
23
+ end
24
+
25
+ def zero?
26
+ @current.zero?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/context/base_context"
4
+ require "trace_viz/context/tracking/depth"
5
+
6
+ module TraceViz
7
+ module Context
8
+ class TrackingContext < BaseContext
9
+ attr_reader :depth
10
+
11
+ def initialize(**options)
12
+ super
13
+
14
+ @depth = Tracking::Depth.new
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/context/manager"
4
+
5
+ module TraceViz
6
+ module Context
7
+ class << self
8
+ def for(type)
9
+ Manager.get_context(type)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/logger"
4
+ require "trace_viz/context"
5
+ require "trace_viz/configuration"
6
+ require "trace_viz/adapters/trace_point_adapter"
7
+
8
+ module TraceViz
9
+ module Core
10
+ class Tracer
11
+ def trace(**options, &block)
12
+ Context::Manager.with_contexts(config: options, tracking: {}) do
13
+ adapter = Adapters::TracePointAdapter.new
14
+ adapter.trace(&block)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/core/tracer"
4
+
5
+ module TraceViz
6
+ module Core
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ class Error < StandardError; end
5
+ class ConfigurationError < Error; end
6
+ class AdapterError < Error; end
7
+ class ContextError < Error; end
8
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ class Logger
5
+ COLORS = {
6
+ reset: "\e[0m",
7
+ info: "\e[34m",
8
+ success: "\e[32m",
9
+ error: "\e[31m",
10
+ warn: "\e[33m",
11
+ start: "\e[36m",
12
+ finish: "\e[35m",
13
+ }.freeze
14
+
15
+ EMOJIS = {
16
+ info: "ℹ️",
17
+ success: "✅",
18
+ error: "❌",
19
+ warn: "⚠️",
20
+ start: "🚀",
21
+ finish: "🏁",
22
+ }.freeze
23
+
24
+ LEVELS = [:info, :success, :error, :warn, :start, :finish].freeze
25
+
26
+ def initialize(output: $stdout)
27
+ @output = output
28
+ end
29
+
30
+ def info(message)
31
+ log(:info, message)
32
+ end
33
+
34
+ def success(message)
35
+ log(:success, message)
36
+ end
37
+
38
+ def error(message)
39
+ log(:error, message)
40
+ end
41
+
42
+ def warn(message)
43
+ log(:warn, message)
44
+ end
45
+
46
+ def start(message)
47
+ log(:start, message)
48
+ end
49
+
50
+ def finish(message)
51
+ log(:finish, message)
52
+ end
53
+
54
+ private
55
+
56
+ def log(level, message)
57
+ return unless LEVELS.include?(level)
58
+
59
+ color = COLORS[level] || COLORS[:info]
60
+ emoji = EMOJIS[level] || EMOJIS[:info]
61
+
62
+ # Align emoji and level using fixed-width columns
63
+ formatted_message = format(
64
+ "#{color}%-3s %-8s#{COLORS[:reset]} %s",
65
+ emoji,
66
+ "[#{level.to_s.upcase}]",
67
+ message,
68
+ )
69
+
70
+ @output.puts(formatted_message)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ module Utils
5
+ module Colorize
6
+ ANSI_COLORS = {
7
+ reset: "\e[0m",
8
+ black: "\e[30m",
9
+ red: "\e[31m",
10
+ green: "\e[32m",
11
+ yellow: "\e[33m",
12
+ blue: "\e[34m",
13
+ magenta: "\e[35m",
14
+ cyan: "\e[36m",
15
+ light_gray: "\e[37m",
16
+ dark_gray: "\e[90m",
17
+ light_red: "\e[91m",
18
+ light_green: "\e[92m",
19
+ light_yellow: "\e[93m",
20
+ light_blue: "\e[94m",
21
+ light_magenta: "\e[95m",
22
+ light_cyan: "\e[96m",
23
+ white: "\e[97m",
24
+ }.freeze
25
+
26
+ class << self
27
+ def colorize(text, color)
28
+ return text unless text
29
+
30
+ "#{ANSI_COLORS[color]}#{text}#{ANSI_COLORS[:reset]}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TraceViz
4
+ VERSION = "0.0.1"
5
+ end
data/lib/trace_viz.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trace_viz/version"
4
+ require "trace_viz/errors"
5
+ require "trace_viz/core"
6
+
7
+ module TraceViz
8
+ class << self
9
+ def trace(**options, &block)
10
+ Core::Tracer.new.trace(**options, &block)
11
+ end
12
+ end
13
+ end