opbeat 2.0.0 → 3.0.0

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/.travis.yml +19 -28
  4. data/.yardopts +3 -0
  5. data/Gemfile +4 -2
  6. data/HISTORY.md +3 -0
  7. data/LICENSE +7 -196
  8. data/README.md +96 -177
  9. data/Rakefile +19 -13
  10. data/gemfiles/Gemfile.base +28 -0
  11. data/gemfiles/Gemfile.rails-3.2.x +3 -0
  12. data/gemfiles/Gemfile.rails-4.0.x +3 -0
  13. data/gemfiles/Gemfile.rails-4.1.x +3 -0
  14. data/gemfiles/Gemfile.rails-4.2.x +3 -0
  15. data/lib/opbeat.rb +113 -93
  16. data/lib/opbeat/capistrano.rb +3 -4
  17. data/lib/opbeat/client.rb +243 -82
  18. data/lib/opbeat/configuration.rb +51 -64
  19. data/lib/opbeat/data_builders.rb +16 -0
  20. data/lib/opbeat/data_builders/error.rb +27 -0
  21. data/lib/opbeat/data_builders/transactions.rb +85 -0
  22. data/lib/opbeat/error.rb +1 -2
  23. data/lib/opbeat/error_message.rb +71 -0
  24. data/lib/opbeat/error_message/exception.rb +12 -0
  25. data/lib/opbeat/error_message/http.rb +62 -0
  26. data/lib/opbeat/error_message/stacktrace.rb +75 -0
  27. data/lib/opbeat/error_message/user.rb +23 -0
  28. data/lib/opbeat/filter.rb +53 -43
  29. data/lib/opbeat/http_client.rb +141 -0
  30. data/lib/opbeat/injections.rb +83 -0
  31. data/lib/opbeat/injections/json.rb +19 -0
  32. data/lib/opbeat/injections/net_http.rb +43 -0
  33. data/lib/opbeat/injections/redis.rb +23 -0
  34. data/lib/opbeat/injections/sequel.rb +32 -0
  35. data/lib/opbeat/injections/sinatra.rb +56 -0
  36. data/lib/opbeat/{capistrano → integration}/capistrano2.rb +6 -6
  37. data/lib/opbeat/{capistrano → integration}/capistrano3.rb +3 -3
  38. data/lib/opbeat/{integrations → integration}/delayed_job.rb +6 -11
  39. data/lib/opbeat/integration/rails/inject_exceptions_catcher.rb +23 -0
  40. data/lib/opbeat/integration/railtie.rb +53 -0
  41. data/lib/opbeat/integration/resque.rb +16 -0
  42. data/lib/opbeat/integration/sidekiq.rb +38 -0
  43. data/lib/opbeat/line_cache.rb +21 -0
  44. data/lib/opbeat/logging.rb +37 -0
  45. data/lib/opbeat/middleware.rb +59 -0
  46. data/lib/opbeat/normalizers.rb +65 -0
  47. data/lib/opbeat/normalizers/action_controller.rb +21 -0
  48. data/lib/opbeat/normalizers/action_view.rb +71 -0
  49. data/lib/opbeat/normalizers/active_record.rb +41 -0
  50. data/lib/opbeat/sql_summarizer.rb +27 -0
  51. data/lib/opbeat/subscriber.rb +80 -0
  52. data/lib/opbeat/tasks.rb +20 -18
  53. data/lib/opbeat/trace.rb +47 -0
  54. data/lib/opbeat/trace_helpers.rb +29 -0
  55. data/lib/opbeat/transaction.rb +99 -0
  56. data/lib/opbeat/util.rb +26 -0
  57. data/lib/opbeat/util/constantize.rb +54 -0
  58. data/lib/opbeat/util/inspector.rb +75 -0
  59. data/lib/opbeat/version.rb +1 -1
  60. data/lib/opbeat/worker.rb +55 -0
  61. data/opbeat.gemspec +6 -14
  62. data/spec/opbeat/client_spec.rb +216 -29
  63. data/spec/opbeat/configuration_spec.rb +34 -38
  64. data/spec/opbeat/data_builders/error_spec.rb +43 -0
  65. data/spec/opbeat/data_builders/transactions_spec.rb +51 -0
  66. data/spec/opbeat/error_message/exception_spec.rb +22 -0
  67. data/spec/opbeat/error_message/http_spec.rb +65 -0
  68. data/spec/opbeat/error_message/stacktrace_spec.rb +56 -0
  69. data/spec/opbeat/error_message/user_spec.rb +28 -0
  70. data/spec/opbeat/error_message_spec.rb +78 -0
  71. data/spec/opbeat/filter_spec.rb +21 -99
  72. data/spec/opbeat/http_client_spec.rb +64 -0
  73. data/spec/opbeat/injections/net_http_spec.rb +37 -0
  74. data/spec/opbeat/injections/sequel_spec.rb +33 -0
  75. data/spec/opbeat/injections/sinatra_spec.rb +13 -0
  76. data/spec/opbeat/injections_spec.rb +49 -0
  77. data/spec/opbeat/integration/delayed_job_spec.rb +35 -0
  78. data/spec/opbeat/integration/json_spec.rb +41 -0
  79. data/spec/opbeat/integration/rails_spec.rb +88 -0
  80. data/spec/opbeat/integration/redis_spec.rb +20 -0
  81. data/spec/opbeat/integration/resque_spec.rb +42 -0
  82. data/spec/opbeat/integration/sidekiq_spec.rb +40 -0
  83. data/spec/opbeat/integration/sinatra_spec.rb +66 -0
  84. data/spec/opbeat/line_cache_spec.rb +38 -0
  85. data/spec/opbeat/logging_spec.rb +47 -0
  86. data/spec/opbeat/middleware_spec.rb +32 -0
  87. data/spec/opbeat/normalizers/action_controller_spec.rb +32 -0
  88. data/spec/opbeat/normalizers/action_view_spec.rb +77 -0
  89. data/spec/opbeat/normalizers/active_record_spec.rb +70 -0
  90. data/spec/opbeat/normalizers_spec.rb +16 -0
  91. data/spec/opbeat/sql_summarizer_spec.rb +6 -0
  92. data/spec/opbeat/subscriber_spec.rb +83 -0
  93. data/spec/opbeat/trace_spec.rb +43 -0
  94. data/spec/opbeat/transaction_spec.rb +98 -0
  95. data/spec/opbeat/util/inspector_spec.rb +40 -0
  96. data/spec/opbeat/util_spec.rb +20 -0
  97. data/spec/opbeat/worker_spec.rb +54 -0
  98. data/spec/opbeat_spec.rb +49 -0
  99. data/spec/spec_helper.rb +79 -6
  100. metadata +89 -149
  101. data/Makefile +0 -3
  102. data/gemfiles/rails30.gemfile +0 -9
  103. data/gemfiles/rails31.gemfile +0 -9
  104. data/gemfiles/rails32.gemfile +0 -9
  105. data/gemfiles/rails40.gemfile +0 -9
  106. data/gemfiles/rails41.gemfile +0 -9
  107. data/gemfiles/rails42.gemfile +0 -9
  108. data/gemfiles/ruby192_rails31.gemfile +0 -10
  109. data/gemfiles/ruby192_rails32.gemfile +0 -10
  110. data/gemfiles/sidekiq31.gemfile +0 -11
  111. data/lib/opbeat/better_attr_accessor.rb +0 -44
  112. data/lib/opbeat/event.rb +0 -223
  113. data/lib/opbeat/integrations/resque.rb +0 -22
  114. data/lib/opbeat/integrations/sidekiq.rb +0 -32
  115. data/lib/opbeat/interfaces.rb +0 -35
  116. data/lib/opbeat/interfaces/exception.rb +0 -16
  117. data/lib/opbeat/interfaces/http.rb +0 -57
  118. data/lib/opbeat/interfaces/message.rb +0 -19
  119. data/lib/opbeat/interfaces/stack_trace.rb +0 -50
  120. data/lib/opbeat/linecache.rb +0 -25
  121. data/lib/opbeat/logger.rb +0 -21
  122. data/lib/opbeat/rack.rb +0 -46
  123. data/lib/opbeat/rails/middleware/debug_exceptions_catcher.rb +0 -22
  124. data/lib/opbeat/railtie.rb +0 -26
  125. data/spec/opbeat/better_attr_accessor_spec.rb +0 -99
  126. data/spec/opbeat/event_spec.rb +0 -138
  127. data/spec/opbeat/integrations/delayed_job_spec.rb +0 -38
  128. data/spec/opbeat/logger_spec.rb +0 -55
  129. data/spec/opbeat/opbeat_spec.rb +0 -64
  130. data/spec/opbeat/rack_spec.rb +0 -117
@@ -1,90 +1,77 @@
1
+ require 'logger'
2
+
1
3
  module Opbeat
2
4
  class Configuration
5
+ DEFAULTS = {
6
+ server: "https://intake.opbeat.com".freeze,
7
+ logger: Logger.new(nil),
8
+ context_lines: 3,
9
+ enabled_environments: %w{production},
10
+ excluded_exceptions: [],
11
+ filter_parameters: [/(authorization|password|passwd|secret)/i],
12
+ timeout: 100,
13
+ open_timeout: 100,
14
+ backoff_multiplier: 2,
15
+ use_ssl: true,
16
+ current_user_method: :current_user,
17
+ environment: ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'default',
18
+ transaction_post_interval: 60,
19
+
20
+ disable_performance: false,
21
+ disable_errors: false,
22
+
23
+ debug_traces: false,
24
+
25
+ # for tests
26
+ disable_worker: false
27
+ }.freeze
3
28
 
4
- # Base URL of the Opbeat server
5
- attr_accessor :server
6
-
7
- # Secret access token for authentication with Opbeat
8
29
  attr_accessor :secret_token
9
-
10
- # Organization ID to use with Opbeat
11
30
  attr_accessor :organization_id
12
-
13
- # App ID to use with Opbeat
14
31
  attr_accessor :app_id
15
32
 
16
- # Logger to use internally
33
+ attr_accessor :server
17
34
  attr_accessor :logger
18
-
19
- # Number of lines of code context to capture, or nil for none
20
35
  attr_accessor :context_lines
21
-
22
- # Whitelist of environments that will send notifications to Opbeat
23
- attr_accessor :environments
24
-
25
- # Which exceptions should never be sent
36
+ attr_accessor :enabled_environments
26
37
  attr_accessor :excluded_exceptions
27
-
28
- # An array of parameters whould should be filtered from the log
29
38
  attr_accessor :filter_parameters
30
-
31
- # Timeout when waiting for the server to return data in seconds
32
39
  attr_accessor :timeout
33
-
34
- # Timout when opening connection to the server
35
40
  attr_accessor :open_timeout
36
-
37
- # Backoff multipler
38
41
  attr_accessor :backoff_multiplier
42
+ attr_accessor :use_ssl
43
+ attr_accessor :current_user_method
44
+ attr_accessor :environment
45
+ attr_accessor :transaction_post_interval
39
46
 
40
- # Should the SSL certificate of the server be verified?
41
- attr_accessor :ssl_verification
47
+ attr_accessor :disable_performance
48
+ attr_accessor :disable_errors
42
49
 
43
- attr_reader :current_environment
50
+ attr_accessor :debug_traces
44
51
 
45
- attr_accessor :user_controller_method
52
+ attr_accessor :disable_worker
46
53
 
47
- # Optional Proc to be used to send events asynchronously
48
- attr_reader :async
54
+ attr_accessor :view_paths
49
55
 
50
- def initialize
51
- self.server = ENV['OPBEAT_SERVER'] || "https://intake.opbeat.com"
52
- self.secret_token = ENV['OPBEAT_SECRET_TOKEN'] if ENV['OPBEAT_SECRET_TOKEN']
53
- self.organization_id = ENV['OPBEAT_ORGANIZATION_ID'] if ENV['OPBEAT_ORGANIZATION_ID']
54
- self.app_id = ENV['OPBEAT_APP_ID'] if ENV['OPBEAT_APP_ID']
55
- @context_lines = 3
56
- self.environments = %w[ development production default ]
57
- self.current_environment = (defined?(::Rails) && ::Rails.env) || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
58
- self.excluded_exceptions = []
59
- self.timeout = 1
60
- self.open_timeout = 1
61
- self.backoff_multiplier = 2
62
- self.ssl_verification = true
63
- self.user_controller_method = 'current_user'
64
- self.async = false
65
- end
56
+ def initialize opts = {}
57
+ DEFAULTS.merge(opts).each do |k, v|
58
+ self.send("#{k}=", v)
59
+ end
66
60
 
67
- # Allows config options to be read like a hash
68
- #
69
- # @param [Symbol] option Key for a given attribute
70
- def [](option)
71
- send(option)
61
+ if block_given?
62
+ yield self
63
+ end
72
64
  end
73
65
 
74
- def current_environment=(environment)
75
- @current_environment = environment.to_s
76
- end
66
+ def validate!
67
+ %w{app_id secret_token organization_id}.each do |key|
68
+ raise Error.new("Configuration missing `#{key}'") unless self.send(key)
69
+ end
77
70
 
78
- def send_in_current_environment?
79
- environments.include? current_environment
71
+ true
72
+ rescue Error => e
73
+ logger.error e.message
74
+ false
80
75
  end
81
-
82
- def async=(value)
83
- raise ArgumentError.new("async must be callable (or false to disable)") unless (value == false || value.respond_to?(:call))
84
- @async = value
85
- end
86
-
87
- alias_method :async?, :async
88
-
89
76
  end
90
77
  end
@@ -0,0 +1,16 @@
1
+ module Opbeat
2
+ # @api private
3
+ module DataBuilders
4
+ class DataBuilder
5
+ def initialize config
6
+ @config = config
7
+ end
8
+
9
+ attr_reader :config
10
+ end
11
+
12
+ %w{transactions error}.each do |f|
13
+ require "opbeat/data_builders/#{f}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'opbeat/filter'
2
+
3
+ module Opbeat
4
+ module DataBuilders
5
+ class Error < DataBuilder
6
+ def build error_message
7
+ h = {
8
+ message: error_message.message,
9
+ timestamp: error_message.timestamp,
10
+ level: error_message.level,
11
+ logger: error_message.logger,
12
+ culprit: error_message.culprit,
13
+ machine: error_message.machine,
14
+ extra: error_message.extra,
15
+ param_message: error_message.param_message
16
+ }
17
+
18
+ h[:exception] = error_message.exception.to_h if error_message.exception
19
+ h[:stacktrace] = error_message.stacktrace.to_h if error_message.stacktrace
20
+ h[:http] = error_message.http.to_h if error_message.http
21
+ h[:user] = error_message.user.to_h if error_message.user
22
+
23
+ h
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ module Opbeat
2
+ module DataBuilders
3
+ class Transactions < DataBuilder
4
+ def build transactions
5
+ reduced = transactions.reduce({ transactions: {}, traces: {} }) do |data, transaction|
6
+ key = [transaction.endpoint, transaction.result, transaction.timestamp]
7
+
8
+ if data[:transactions][key].nil?
9
+ data[:transactions][key] = build_transaction(transaction)
10
+ else
11
+ data[:transactions][key][:durations] << ms(transaction.duration)
12
+ end
13
+
14
+ combine_traces transaction.traces, data[:traces]
15
+
16
+ data
17
+ end.reduce({}) do |data, kv|
18
+ key, collection = kv
19
+ data[key] = collection.values
20
+ data
21
+ end
22
+
23
+ reduced[:traces].each do |trace|
24
+ # traces' start time is average across collected
25
+ trace[:start_time] = trace[:start_time].reduce(0, :+) / trace[:start_time].length
26
+ end
27
+
28
+ # preserve root
29
+ root = reduced[:traces].shift
30
+ # re-add root
31
+ reduced[:traces].unshift root
32
+
33
+ reduced
34
+ end
35
+
36
+ private
37
+
38
+ def combine_traces traces, into
39
+ traces.each do |trace|
40
+ key = [trace.transaction.endpoint, trace.signature, trace.timestamp]
41
+
42
+ if into[key].nil?
43
+ into[key] = build_trace(trace)
44
+ else
45
+ into[key][:durations] << [
46
+ ms(trace.duration),
47
+ ms(trace.transaction.duration)
48
+ ]
49
+ into[key][:start_time] << ms(trace.relative_start)
50
+ end
51
+ end
52
+ end
53
+
54
+ def build_transaction transaction
55
+ {
56
+ transaction: transaction.endpoint,
57
+ result: transaction.result,
58
+ kind: transaction.kind,
59
+ timestamp: transaction.timestamp,
60
+ durations: [ms(transaction.duration)]
61
+ }
62
+ end
63
+
64
+ def build_trace trace
65
+ {
66
+ transaction: trace.transaction.endpoint,
67
+ signature: trace.signature,
68
+ durations: [[
69
+ ms(trace.duration),
70
+ ms(trace.transaction.duration)
71
+ ]],
72
+ start_time: [ms(trace.relative_start)],
73
+ kind: trace.kind,
74
+ timestamp: trace.timestamp,
75
+ parents: trace.parents && trace.parents.map(&:signature) || [],
76
+ extra: trace.extra
77
+ }
78
+ end
79
+
80
+ def ms nanos
81
+ nanos.to_f / 1_000_000
82
+ end
83
+ end
84
+ end
85
+ end
data/lib/opbeat/error.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  module Opbeat
2
-
2
+ # @api private
3
3
  class Error < StandardError
4
4
  end
5
-
6
5
  end
@@ -0,0 +1,71 @@
1
+ require 'opbeat/line_cache'
2
+ require 'opbeat/error_message/exception'
3
+ require 'opbeat/error_message/stacktrace'
4
+ require 'opbeat/error_message/http'
5
+ require 'opbeat/error_message/user'
6
+
7
+ module Opbeat
8
+ class ErrorMessage
9
+ extend Logging
10
+
11
+ DEFAULTS = {
12
+ level: :error,
13
+ logger: 'root'.freeze
14
+ }.freeze
15
+
16
+ def initialize config, message, attrs = {}
17
+ @config = config
18
+
19
+ @message = message
20
+ @timestamp = Time.now.utc.to_i
21
+ DEFAULTS.merge(attrs).each do |k,v|
22
+ send(:"#{k}=", v)
23
+ end
24
+ @filter = Filter.new config
25
+
26
+ yield self if block_given?
27
+ end
28
+
29
+ attr_reader :config
30
+ attr_accessor :message
31
+ attr_reader :timestamp
32
+ attr_accessor :level
33
+ attr_accessor :logger
34
+ attr_accessor :culprit
35
+ attr_accessor :machine
36
+ attr_accessor :extra
37
+ attr_accessor :param_message
38
+ attr_accessor :exception
39
+ attr_accessor :stacktrace
40
+ attr_accessor :http
41
+ attr_accessor :user
42
+
43
+ def self.from_exception config, exception, opts = {}
44
+ message = "#{exception.class}: #{exception.message}"
45
+
46
+ if config.excluded_exceptions.include? exception.class.to_s
47
+ info "Skipping excluded exception #{exception.class}"
48
+ return nil
49
+ end
50
+
51
+ error_message = new(config, message) do |msg|
52
+ msg.level = :error
53
+ msg.exception = Exception.from(exception)
54
+ msg.stacktrace = Stacktrace.from(config, exception)
55
+ end
56
+
57
+ if frames = error_message.stacktrace && error_message.stacktrace.frames
58
+ if first_frame = frames[0]
59
+ error_message.culprit = "#{first_frame.filename}:#{first_frame.lineno}:in `#{first_frame.function}'"
60
+ end
61
+ end
62
+
63
+ if env = opts[:rack_env]
64
+ error_message.http = HTTP.from_rack_env env, filter: @filter
65
+ error_message.user = User.from_rack_env config, env
66
+ end
67
+
68
+ error_message
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,12 @@
1
+ module Opbeat
2
+ class ErrorMessage
3
+ class Exception < Struct.new(:type, :value, :module)
4
+ SPLIT = '::'.freeze
5
+
6
+ def self.from exception
7
+ new exception.class.to_s, exception.message,
8
+ exception.class.to_s.split(SPLIT)[0...-1].join(SPLIT)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ module Opbeat
2
+ class ErrorMessage
3
+ class HTTP < Struct.new(:url, :method, :data, :query_string, :cookies,
4
+ :headers, :remote_host, :http_host, :user_agent,
5
+ :secure, :env)
6
+
7
+ HTTP_ENV_KEY = /^HTTP_/.freeze
8
+ UNDERSCORE = "_".freeze
9
+ DASH = "-".freeze
10
+ QUESTION = "?".freeze
11
+
12
+ def self.from_rack_env env, opts = {}
13
+ req = Rack::Request.new env
14
+
15
+ http = new(
16
+ req.url.split(QUESTION).first, # url
17
+ req.request_method, # method
18
+ nil, # data
19
+ req.query_string, # query string
20
+ env['HTTP_COOKIE'], # cookies
21
+ {}, # headers
22
+ req.ip, # remote host
23
+ req.host_with_port, # http host
24
+ req.user_agent, # user agent
25
+ req.scheme == 'https'.freeze ? true : false, # secure
26
+ {} # env
27
+ )
28
+
29
+ env.each do |k, v|
30
+ next unless k.upcase == k # lower case stuff isn't relevant
31
+
32
+ if k.match(HTTP_ENV_KEY)
33
+ header = k.gsub(HTTP_ENV_KEY, '')
34
+ .split(UNDERSCORE).map(&:capitalize).join(DASH)
35
+ http.headers[header] = v.to_s
36
+ else
37
+ http.env[k] = v.to_s
38
+ end
39
+ end
40
+
41
+ if req.form_data?
42
+ http.data = req.POST
43
+ elsif req.body
44
+ http.data = req.body.read
45
+ req.body.rewind
46
+ end
47
+
48
+ if filter = opts[:filter]
49
+ http.apply_filter filter
50
+ end
51
+
52
+ http
53
+ end
54
+
55
+ def apply_filter filter
56
+ self.data = filter.apply data
57
+ self.query_string = filter.apply query_string
58
+ self.cookies = filter.apply cookies
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,75 @@
1
+ module Opbeat
2
+ class ErrorMessage
3
+ class Stacktrace
4
+
5
+ def initialize config, frames
6
+ @config, @frames = config, frames
7
+ end
8
+
9
+ attr_reader :frames
10
+
11
+ def self.from config, exception
12
+ return unless exception.backtrace
13
+
14
+ new(config, exception.backtrace.map do |line|
15
+ Frame.from_line config, line
16
+ end)
17
+ end
18
+
19
+ def to_h
20
+ { frames: frames.map(&:to_h) }
21
+ end
22
+
23
+ private
24
+
25
+ class Frame < Struct.new(:filename, :lineno, :abs_path, :function, :vars,
26
+ :pre_context, :context_line, :post_context)
27
+
28
+ BACKTRACE_REGEX = /^(.+?):(\d+)(?::in `(.+?)')?$/.freeze
29
+
30
+ class << self
31
+ def from_line config, line
32
+ _, abs_path, lineno, function = line.match(BACKTRACE_REGEX).to_a
33
+ lineno = lineno.to_i
34
+ filename = strip_load_path(abs_path)
35
+
36
+ if lines = config.context_lines
37
+ pre_context, context_line, post_context =
38
+ get_contextlines(abs_path, lineno, lines)
39
+ end
40
+
41
+ new filename, lineno, abs_path, function, nil,
42
+ pre_context, context_line, post_context
43
+ end
44
+
45
+ private
46
+
47
+ def strip_load_path path
48
+ prefix = $:
49
+ .map(&:to_s)
50
+ .select { |s| path.start_with?(s) }
51
+ .sort_by { |s| s.length }
52
+ .last
53
+
54
+ return path unless prefix
55
+
56
+ path[prefix.chomp(File::SEPARATOR).length + 1..-1]
57
+ end
58
+
59
+ def get_contextlines path, line, context
60
+ lines = (2 * context + 1).times.map do |i|
61
+ LineCache.find(path, line - context + i)
62
+ end
63
+
64
+ pre = lines[0..(context-1)]
65
+ line = lines[context]
66
+ post = lines[(context+1)..-1]
67
+
68
+ [pre, line, post]
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end