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