summaryrefslogtreecommitdiff
path: root/lib/straw.rb
blob: d357df1a22082b813fa4e977e040af5a4f008d20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# frozen_string_literal: true

require "logger"
require_relative "straw/version"

module Straw
  class Error < StandardError; end

  def self.logger
    @logger ||= Logger.new($stderr, level: ENV.fetch("LOG_LEVEL", Logger::INFO)).tap do |x|
      x.formatter = proc do |_severity, _datetime, _progname, message|
        "[#{VERSION}] #{message}\n"
      end
    end
  end

  def self.tracer
    @tracer ||= Tracer.new(logger)
  end

  module Memoizable
    def memoize(key)
      if memoized?(key)
        instance_variable_get(var_for(key))
      else
        instance_variable_set(var_for(key), yield)
      end
    end

    def memoized?(key)
      instance_variable_defined?(var_for(key))
    end

    private

    def var_for(key)
      "@#{key}"
    end
  end

  class Tracer
    def initialize(logger)
      @logger = logger
    end

    def trace(defaults = {})
      tracer = TracePoint.new(:call) do |x|
        @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) }))
      rescue StandardError => boom
        @logger.error(defaults.merge({ message: boom.message, stacktrace: boom.backtrace }))
      end
      tracer.enable
      yield
    ensure
      tracer.disable
    end

    private

    def args_from(trace)
      trace.parameters.map(&:last).map { |x| [x, trace.binding.eval(x.to_s)] }.to_h
    end

    def locals_from(trace)
      trace.binding.local_variables.map { |x| [x, trace.binding.local_variable_get(x)] }.to_h
    end
  end
end