instana 1.1.0 → 1.2.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +24 -2
  5. data/Gemfile +2 -22
  6. data/README.md +1 -1
  7. data/Rakefile +15 -0
  8. data/Tracing.md +29 -2
  9. data/gemfiles/libraries.gemfile +50 -0
  10. data/gemfiles/rails32.gemfile +45 -0
  11. data/gemfiles/rails42.gemfile +44 -0
  12. data/gemfiles/rails50.gemfile +44 -0
  13. data/instana.gemspec +6 -8
  14. data/lib/instana/config.rb +7 -5
  15. data/lib/instana/frameworks/instrumentation/abstract_mysql_adapter.rb +55 -0
  16. data/lib/instana/frameworks/instrumentation/action_controller.rb +105 -0
  17. data/lib/instana/frameworks/instrumentation/active_record.rb +22 -0
  18. data/lib/instana/frameworks/instrumentation/mysql2_adapter.rb +81 -0
  19. data/lib/instana/frameworks/instrumentation/mysql_adapter.rb +56 -0
  20. data/lib/instana/frameworks/instrumentation/postgresql_adapter.rb +71 -0
  21. data/lib/instana/frameworks/rails.rb +5 -0
  22. data/lib/instana/test.rb +40 -0
  23. data/lib/instana/tracer.rb +19 -0
  24. data/lib/instana/tracing/span.rb +3 -3
  25. data/lib/instana/version.rb +1 -1
  26. data/log/.keep +0 -0
  27. data/test/agent/agent_test.rb +139 -0
  28. data/test/apps/cuba.rb +15 -0
  29. data/test/apps/roda.rb +10 -0
  30. data/test/apps/sinatra.rb +5 -0
  31. data/test/config_test.rb +17 -0
  32. data/test/frameworks/cuba_test.rb +44 -0
  33. data/test/frameworks/rack_test.rb +152 -0
  34. data/test/frameworks/rails/actioncontroller_test.rb +123 -0
  35. data/test/frameworks/rails/activerecord3_test.rb +134 -0
  36. data/test/frameworks/rails/activerecord4_test.rb +134 -0
  37. data/test/frameworks/rails/activerecord5_test.rb +90 -0
  38. data/test/frameworks/roda_test.rb +44 -0
  39. data/test/frameworks/sinatra_test.rb +44 -0
  40. data/test/instana_test.rb +27 -0
  41. data/test/instrumentation/dalli_test.rb +274 -0
  42. data/test/instrumentation/excon_test.rb +171 -0
  43. data/test/instrumentation/net-http_test.rb +140 -0
  44. data/test/instrumentation/rest-client_test.rb +61 -0
  45. data/test/models/block.rb +18 -0
  46. data/test/servers/rackapp_6511.rb +20 -0
  47. data/test/servers/rails_3205.rb +95 -0
  48. data/test/test_helper.rb +39 -0
  49. data/test/tracing/custom_test.rb +143 -0
  50. data/test/tracing/id_management_test.rb +96 -0
  51. data/test/tracing/opentracing_test.rb +377 -0
  52. data/test/tracing/trace_test.rb +50 -0
  53. data/test/tracing/tracer_async_test.rb +298 -0
  54. data/test/tracing/tracer_test.rb +202 -0
  55. metadata +114 -4
@@ -1,4 +1,4 @@
1
1
  module Instana
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  VERSION_FULL = "instana-#{VERSION}"
4
4
  end
File without changes
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+
3
+ class AgentTest < Minitest::Test
4
+ def test_agent_host_detection
5
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/"
6
+ stub_request(:get, url)
7
+ assert_equal true, ::Instana.agent.host_agent_ready?
8
+ end
9
+
10
+ def test_successful_discovery
11
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/"
12
+ docker_url = "http://#{::Instana.agent.instance_variable_get(:@default_gateway)}:#{::Instana.config[:agent_port]}/"
13
+ stub_request(:get, url)
14
+ stub_request(:get, docker_url)
15
+ discovered = ::Instana.agent.run_discovery
16
+
17
+ assert discovered.is_a?(Hash)
18
+ assert_equal "127.0.0.1", discovered[:agent_host]
19
+ assert_equal 42699, discovered[:agent_port]
20
+ end
21
+
22
+ def test_failed_discovery
23
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/"
24
+ docker_url = "http://#{::Instana.agent.instance_variable_get(:@default_gateway)}:#{::Instana.config[:agent_port]}/"
25
+ stub_request(:get, url).to_raise(Errno::ECONNREFUSED)
26
+ stub_request(:get, docker_url).to_raise(Errno::ECONNREFUSED)
27
+ discovered = ::Instana.agent.run_discovery
28
+
29
+ assert_equal nil, discovered
30
+ end
31
+
32
+ def test_custom_configure_agent
33
+ original_host = ::Instana.config[:agent_host]
34
+ original_port = ::Instana.config[:agent_port]
35
+
36
+ # Set custom agent/port
37
+ ::Instana.config[:agent_host] = '172.0.12.100'
38
+ ::Instana.config[:agent_port] = 12829
39
+
40
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/"
41
+ stub_request(:get, url)
42
+ discovered = ::Instana.agent.run_discovery
43
+
44
+ assert discovered.is_a?(Hash)
45
+ assert_equal "172.0.12.100", discovered[:agent_host]
46
+ assert_equal 12829, discovered[:agent_port]
47
+
48
+ ::Instana.config[:agent_host] = original_host
49
+ ::Instana.config[:agent_port] = original_port
50
+ end
51
+
52
+ def test_no_host_agent
53
+ localhost_url = "http://#{::Instana::Agent::LOCALHOST}:#{::Instana.config[:agent_port]}/"
54
+ stub_request(:get, localhost_url).to_raise(Errno::ECONNREFUSED)
55
+ docker_url = "http://#{::Instana.agent.instance_variable_get(:@default_gateway)}:#{::Instana.config[:agent_port]}/"
56
+ stub_request(:get, docker_url).to_timeout
57
+ assert_equal false, ::Instana.agent.host_agent_ready?
58
+ end
59
+
60
+ def test_announce_sensor
61
+ # Fake discovery values
62
+ discovery = {}
63
+ discovery[:agent_host] = ::Instana.config[:agent_host]
64
+ discovery[:agent_port] = ::Instana.config[:agent_port]
65
+ ::Instana.agent.instance_variable_set(:@discovered, discovery)
66
+
67
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.discovery"
68
+ json = { 'pid' => Process.pid, 'agentUuid' => 'abc' }.to_json
69
+ stub_request(:put, url).to_return(:body => json, :status => 200)
70
+
71
+ assert_equal true, ::Instana.agent.announce_sensor
72
+ end
73
+
74
+ def test_failed_announce_sensor
75
+ # Fake discovery values
76
+ discovery = {}
77
+ discovery[:agent_host] = ::Instana.config[:agent_host]
78
+ discovery[:agent_port] = ::Instana.config[:agent_port]
79
+ ::Instana.agent.instance_variable_set(:@discovered, discovery)
80
+
81
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.discovery"
82
+ stub_request(:put, url).to_raise(Errno::ECONNREFUSED)
83
+
84
+ assert_equal false, ::Instana.agent.announce_sensor
85
+ end
86
+
87
+ def test_metric_report
88
+ # Fake discovery values
89
+ discovery = {}
90
+ discovery[:agent_host] = ::Instana.config[:agent_host]
91
+ discovery[:agent_port] = ::Instana.config[:agent_port]
92
+ ::Instana.agent.instance_variable_set(:@discovered, discovery)
93
+
94
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.discovery"
95
+ json = { 'pid' => Process.pid, 'agentUuid' => 'abc' }.to_json
96
+ stub_request(:put, url).to_return(:body => json, :status => 200)
97
+ ::Instana.agent.announce_sensor
98
+
99
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.#{Process.pid}"
100
+ stub_request(:post, url)
101
+
102
+ payload = { :test => 'true' }
103
+ assert_equal true, ::Instana.agent.report_metrics(payload)
104
+ end
105
+
106
+ def test_failed_metric_report
107
+ # Fake discovery values
108
+ discovery = {}
109
+ discovery[:agent_host] = ::Instana.config[:agent_host]
110
+ discovery[:agent_port] = ::Instana.config[:agent_port]
111
+ ::Instana.agent.instance_variable_set(:@discovered, discovery)
112
+
113
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.discovery"
114
+ json = { 'pid' => Process.pid, 'agentUuid' => 'abc' }.to_json
115
+ stub_request(:put, url).to_return(:body => json, :status => 200)
116
+
117
+ ::Instana.agent.announce_sensor
118
+
119
+ url = "http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/com.instana.plugin.ruby.#{Process.pid}"
120
+ stub_request(:post, url).to_raise(Errno::ECONNREFUSED)
121
+
122
+ payload = { :test => 'true' }
123
+ assert_equal false, ::Instana.agent.report_metrics(payload)
124
+ end
125
+
126
+ def test_agent_timeout
127
+ # Fake discovery values
128
+ discovery = {}
129
+ discovery[:agent_host] = ::Instana.config[:agent_host]
130
+ discovery[:agent_port] = ::Instana.config[:agent_port]
131
+ ::Instana.agent.instance_variable_set(:@discovered, discovery)
132
+
133
+ localhost_url = "http://#{::Instana::Agent::LOCALHOST}:#{::Instana.config[:agent_port]}/"
134
+ stub_request(:get, localhost_url).to_timeout
135
+ docker_url = "http://#{::Instana.agent.instance_variable_get(:@default_gateway)}:#{::Instana.config[:agent_port]}/"
136
+ stub_request(:get, docker_url).to_timeout
137
+ assert_equal false, ::Instana.agent.host_agent_ready?
138
+ end
139
+ end
@@ -0,0 +1,15 @@
1
+ require "cuba/safe"
2
+
3
+ Cuba.plugin ::Cuba::Safe
4
+
5
+ Cuba.define do
6
+ on get do
7
+ on "hello" do
8
+ res.write "Hello Instana!"
9
+ end
10
+
11
+ on root do
12
+ res.redirect '/hello'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ class InstanaRodaApp < Roda
2
+ route do |r|
3
+ r.root do
4
+ r.redirect '/hello'
5
+ end
6
+ r.get "hello" do
7
+ "Hello Roda + Instana"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class InstanaSinatraApp < ::Sinatra::Base
2
+ get '/' do
3
+ "Hello Sinatra!"
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ class ConfigTest < Minitest::Test
4
+ def test_that_config_exists
5
+ refute_nil ::Instana.config
6
+ assert_instance_of(::Instana::Config, ::Instana.config)
7
+ end
8
+
9
+ def test_that_it_has_defaults
10
+ assert_equal '127.0.0.1', ::Instana.config[:agent_host]
11
+ assert_equal 42699, ::Instana.config[:agent_port]
12
+
13
+ ::Instana.config[:metrics].each do |k, v|
14
+ assert_equal true, ::Instana.config[:metrics][k].key?(:enabled)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+
2
+ if defined?(::Cuba)
3
+ require 'test_helper'
4
+ require File.expand_path(File.dirname(__FILE__) + '/../apps/cuba')
5
+ require 'rack/test'
6
+
7
+ class CubaTest < Minitest::Test
8
+ include Rack::Test::Methods
9
+
10
+ def app
11
+ Cuba
12
+ end
13
+
14
+ def test_basic_get
15
+ clear_all!
16
+
17
+ r = get '/hello'
18
+ assert last_response.ok?
19
+
20
+ assert r.headers.key?("X-Instana-T")
21
+ assert r.headers.key?("X-Instana-S")
22
+
23
+ spans = ::Instana.processor.queued_spans
24
+ assert_equal 1, spans.count
25
+
26
+ first_span = spans.first
27
+ assert_equal :rack, first_span[:n]
28
+ assert first_span.key?(:data)
29
+ assert first_span[:data].key?(:http)
30
+
31
+ assert first_span[:data][:http].key?(:method)
32
+ assert_equal "GET", first_span[:data][:http][:method]
33
+
34
+ assert first_span[:data][:http].key?(:url)
35
+ assert_equal "/hello", first_span[:data][:http][:url]
36
+
37
+ assert first_span[:data][:http].key?(:status)
38
+ assert_equal 200, first_span[:data][:http][:status]
39
+
40
+ assert first_span[:data][:http].key?(:host)
41
+ assert_equal "example.org", first_span[:data][:http][:host]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,152 @@
1
+ require 'test_helper'
2
+ require 'rack/test'
3
+ require 'rack/lobster'
4
+ require "instana/rack"
5
+
6
+ class RackTest < Minitest::Test
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app = Rack::Builder.new {
11
+ use Rack::CommonLogger
12
+ use Rack::ShowExceptions
13
+ use Instana::Rack
14
+ map "/mrlobster" do
15
+ run Rack::Lobster.new
16
+ end
17
+ }
18
+ end
19
+
20
+ def test_basic_get
21
+ clear_all!
22
+ get '/mrlobster'
23
+ assert last_response.ok?
24
+
25
+ spans = ::Instana.processor.queued_spans
26
+
27
+ # Span validation
28
+ assert_equal 1, spans.count
29
+
30
+ first_span = spans.first
31
+ assert_equal :rack, first_span[:n]
32
+ assert_equal :ruby, first_span[:ta]
33
+ assert first_span.key?(:data)
34
+ assert first_span[:data].key?(:http)
35
+ assert_equal "GET", first_span[:data][:http][:method]
36
+ assert_equal "/mrlobster", first_span[:data][:http][:url]
37
+ assert_equal 200, first_span[:data][:http][:status]
38
+ assert_equal 'example.org', first_span[:data][:http][:host]
39
+ assert first_span.key?(:f)
40
+ assert first_span[:f].key?(:e)
41
+ assert first_span[:f].key?(:h)
42
+ assert_equal ::Instana.agent.agent_uuid, first_span[:f][:h]
43
+
44
+ # Backtrace fingerprint validation
45
+ assert first_span.key?(:stack)
46
+ assert_equal 2, first_span[:stack].count
47
+ refute_nil first_span[:stack].first[:f].match(/instana\/instrumentation\/rack.rb/)
48
+ end
49
+
50
+ def test_basic_post
51
+ clear_all!
52
+ post '/mrlobster'
53
+ assert last_response.ok?
54
+
55
+ spans = ::Instana.processor.queued_spans
56
+
57
+ # Span validation
58
+ assert_equal 1, spans.count
59
+ first_span = spans.first
60
+ assert_equal :rack, first_span[:n]
61
+ assert_equal :ruby, first_span[:ta]
62
+ assert first_span.key?(:data)
63
+ assert first_span[:data].key?(:http)
64
+ assert_equal "POST", first_span[:data][:http][:method]
65
+ assert_equal "/mrlobster", first_span[:data][:http][:url]
66
+ assert_equal 200, first_span[:data][:http][:status]
67
+ assert first_span.key?(:f)
68
+ assert first_span[:f].key?(:e)
69
+ assert first_span[:f].key?(:h)
70
+ assert_equal ::Instana.agent.agent_uuid, first_span[:f][:h]
71
+ end
72
+
73
+ def test_basic_put
74
+ clear_all!
75
+ put '/mrlobster'
76
+ assert last_response.ok?
77
+
78
+ spans = ::Instana.processor.queued_spans
79
+
80
+ # Span validation
81
+ assert_equal 1, spans.count
82
+ first_span = spans.first
83
+ assert_equal :rack, first_span[:n]
84
+ assert_equal :ruby, first_span[:ta]
85
+ assert first_span.key?(:data)
86
+ assert first_span[:data].key?(:http)
87
+ assert_equal "PUT", first_span[:data][:http][:method]
88
+ assert_equal "/mrlobster", first_span[:data][:http][:url]
89
+ assert_equal 200, first_span[:data][:http][:status]
90
+ assert first_span.key?(:f)
91
+ assert first_span[:f].key?(:e)
92
+ assert first_span[:f].key?(:h)
93
+ assert_equal ::Instana.agent.agent_uuid, first_span[:f][:h]
94
+ end
95
+
96
+ def test_context_continuation
97
+ clear_all!
98
+ header 'X-INSTANA-T', Instana::Util.id_to_header(1234)
99
+ header 'X-INSTANA-S', Instana::Util.id_to_header(4321)
100
+
101
+ get '/mrlobster'
102
+ assert last_response.ok?
103
+
104
+ spans = ::Instana.processor.queued_spans
105
+
106
+ # Span validation
107
+ assert_equal 1, spans.count
108
+ first_span = spans.first
109
+ assert_equal :rack, first_span[:n]
110
+ assert_equal :ruby, first_span[:ta]
111
+ assert first_span.key?(:data)
112
+ assert first_span[:data].key?(:http)
113
+ assert_equal "GET", first_span[:data][:http][:method]
114
+ assert_equal "/mrlobster", first_span[:data][:http][:url]
115
+ assert_equal 200, first_span[:data][:http][:status]
116
+ assert first_span.key?(:f)
117
+ assert first_span[:f].key?(:e)
118
+ assert first_span[:f].key?(:h)
119
+ assert_equal ::Instana.agent.agent_uuid, first_span[:f][:h]
120
+
121
+ # Context validation
122
+ # The first span should have the passed in trace ID
123
+ # and specify the passed in span ID as it's parent.
124
+ assert_equal 1234, first_span[:t]
125
+ assert_equal 4321, first_span[:p]
126
+ end
127
+
128
+ def test_instana_response_headers
129
+ clear_all!
130
+ get '/mrlobster'
131
+ assert last_response.ok?
132
+
133
+ refute_nil last_response.headers.key?("X-Instana-T")
134
+ refute_nil last_response.headers.key?("X-Instana-S")
135
+ end
136
+
137
+ def test_that_url_params_not_logged
138
+ clear_all!
139
+ get '/mrlobster?blah=2&wilma=1&betty=2;fred=3'
140
+
141
+ traces = ::Instana.processor.queued_traces
142
+ assert_equal 1, traces.count
143
+
144
+ trace = traces[0]
145
+ refute_nil trace.spans.first.key?(:data)
146
+ refute_nil trace.spans.first[:data].key?(:http)
147
+ refute_nil trace.spans.first[:data][:http].key?(:url)
148
+ assert_equal '/mrlobster', trace.spans.first[:data][:http][:url]
149
+
150
+ assert last_response.ok?
151
+ end
152
+ end
@@ -0,0 +1,123 @@
1
+ require 'test_helper'
2
+
3
+ class ActionControllerTest < Minitest::Test
4
+ def test_config_defaults
5
+ assert ::Instana.config[:action_controller].is_a?(Hash)
6
+ assert ::Instana.config[:action_controller].key?(:enabled)
7
+ assert_equal true, ::Instana.config[:action_controller][:enabled]
8
+ end
9
+
10
+ def test_controller_reporting
11
+ clear_all!
12
+
13
+ Net::HTTP.get(URI.parse('http://localhost:3205/test/world'))
14
+
15
+ traces = Instana.processor.queued_traces
16
+ assert_equal 1, traces.count
17
+ trace = traces.first
18
+
19
+ assert_equal 2, trace.spans.count
20
+ spans = trace.spans.to_a
21
+ first_span = spans[0]
22
+ second_span = spans[1]
23
+
24
+ assert_equal :rack, first_span.name
25
+
26
+ assert_equal :actioncontroller, second_span.name
27
+ assert_equal "TestController", second_span[:data][:actioncontroller][:controller]
28
+ assert_equal "world", second_span[:data][:actioncontroller][:action]
29
+ end
30
+
31
+ def test_controller_error
32
+ clear_all!
33
+
34
+ Net::HTTP.get(URI.parse('http://localhost:3205/test/error'))
35
+
36
+ traces = Instana.processor.queued_traces
37
+ assert_equal 1, traces.count
38
+ trace = traces.first
39
+
40
+ assert_equal 2, trace.spans.count
41
+ spans = trace.spans.to_a
42
+ first_span = spans[0]
43
+ second_span = spans[1]
44
+
45
+ assert_equal :rack, first_span.name
46
+
47
+ assert_equal :actioncontroller, second_span.name
48
+ assert_equal "TestController", second_span[:data][:actioncontroller][:controller]
49
+ assert_equal "error", second_span[:data][:actioncontroller][:action]
50
+ assert second_span.key?(:error)
51
+ assert second_span.key?(:stack)
52
+ assert_equal "Warning: This is a simulated Error", second_span[:data][:log][:message]
53
+ assert_equal "Exception", second_span[:data][:log][:parameters]
54
+ end
55
+
56
+ def test_api_controller_reporting
57
+ # Run only when ActionController::API is used/defined
58
+ skip unless defined?(::ActionController::API)
59
+
60
+ clear_all!
61
+
62
+ Net::HTTP.get(URI.parse('http://localhost:3205/api/world'))
63
+
64
+ traces = Instana.processor.queued_traces
65
+ assert_equal 1, traces.count
66
+ trace = traces.first
67
+
68
+ assert_equal 2, trace.spans.count
69
+ spans = trace.spans.to_a
70
+ first_span = spans[0]
71
+ second_span = spans[1]
72
+
73
+ assert_equal :rack, first_span.name
74
+
75
+ assert_equal :actioncontroller, second_span.name
76
+ assert_equal "SocketController", second_span[:data][:actioncontroller][:controller]
77
+ assert_equal "world", second_span[:data][:actioncontroller][:action]
78
+ end
79
+
80
+ def test_api_controller_error
81
+ # Run only when ActionController::API is used/defined
82
+ skip unless defined?(::ActionController::API)
83
+
84
+ clear_all!
85
+
86
+ Net::HTTP.get(URI.parse('http://localhost:3205/api/error'))
87
+
88
+ traces = Instana.processor.queued_traces
89
+ assert_equal 1, traces.count
90
+ trace = traces.first
91
+
92
+ assert_equal 2, trace.spans.count
93
+ spans = trace.spans.to_a
94
+ first_span = spans[0]
95
+ second_span = spans[1]
96
+
97
+ assert_equal :rack, first_span.name
98
+
99
+ assert_equal :actioncontroller, second_span.name
100
+ assert_equal "SocketController", second_span[:data][:actioncontroller][:controller]
101
+ assert_equal "error", second_span[:data][:actioncontroller][:action]
102
+ assert second_span.key?(:error)
103
+ assert second_span.key?(:stack)
104
+ assert_equal "Warning: This is a simulated Socket API Error", second_span[:data][:log][:message]
105
+ assert_equal "Exception", second_span[:data][:log][:parameters]
106
+ end
107
+
108
+ def test_404
109
+ clear_all!
110
+
111
+ Net::HTTP.get(URI.parse('http://localhost:3205/test/404'))
112
+
113
+ traces = Instana.processor.queued_traces
114
+ assert_equal 1, traces.count
115
+ trace = traces.first
116
+
117
+ assert_equal 1, trace.spans.count
118
+ spans = trace.spans.to_a
119
+ first_span = spans[0]
120
+
121
+ assert_equal :rack, first_span.name
122
+ end
123
+ end