main
1# frozen_string_literal: true
2
3require "logger"
4require_relative "straw/version"
5
6module Straw
7 class Error < StandardError; end
8
9 def self.logger
10 @logger ||= Logger.new($stderr, level: ENV.fetch("LOG_LEVEL", Logger::INFO)).tap do |x|
11 x.formatter = proc do |_severity, _datetime, _progname, message|
12 "[#{VERSION}] #{message}\n"
13 end
14 end
15 end
16
17 def self.tracer
18 @tracer ||= Tracer.new(logger)
19 end
20
21 module Memoizable
22 def memoize(key)
23 if memoized?(key)
24 instance_variable_get(var_for(key))
25 else
26 instance_variable_set(var_for(key), yield)
27 end
28 end
29
30 def memoized?(key)
31 instance_variable_defined?(var_for(key))
32 end
33
34 private
35
36 def var_for(key)
37 "@#{key}"
38 end
39 end
40
41 class Tracer
42 def initialize(logger)
43 @logger = logger
44 end
45
46 def trace(defaults = {})
47 tracer = TracePoint.new(:call) do |x|
48 @logger.debug(defaults.merge({ path: x.path, lineno: x.lineno, clazz: x.defined_class, method: x.method_id, args: args_from(x), locals: locals_from(x) }))
49 rescue StandardError => boom
50 @logger.error(defaults.merge({ message: boom.message, stacktrace: boom.backtrace }))
51 end
52 tracer.enable
53 yield
54 ensure
55 tracer.disable
56 end
57
58 private
59
60 def args_from(trace)
61 trace.parameters.map(&:last).map { |x| [x, trace.binding.eval(x.to_s)] }.to_h
62 end
63
64 def locals_from(trace)
65 trace.binding.local_variables.map { |x| [x, trace.binding.local_variable_get(x)] }.to_h
66 end
67 end
68end