Skip to content

Commit 5a9ce41

Browse files
committed
Merge pull request gjtorikian#46 from mtodd/instrument-doc
Include output and results to filter, pipeline event payload
2 parents 21430a6 + f1802cd commit 5a9ce41

File tree

4 files changed

+91
-27
lines changed

4 files changed

+91
-27
lines changed

README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,10 @@ Pipeline.new [ RootRelativeFilter ], { :base_url => 'http://somehost.com' }
198198

199199
## Instrumenting
200200

201-
To instrument each filter and a full pipeline call, set an
202-
[ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
203-
service object on a pipeline object. New pipeline objects will default to the
201+
Filters and Pipelines can be set up to be instrumented when called. The pipeline
202+
must be setup with an [ActiveSupport::Notifications]
203+
(http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
204+
compatible service object and a name. New pipeline objects will default to the
204205
`HTML::Pipeline.default_instrumentation_service` object.
205206

206207
``` ruby
@@ -209,10 +210,12 @@ service = ActiveSupport::Notifications
209210

210211
# instrument a specific pipeline
211212
pipeline = HTML::Pipeline.new [MarkdownFilter], context
212-
pipeline.instrumentation_service = service
213+
pipeline.setup_instrumentation "MarkdownPipeline", service
213214

214-
# or instrument all new pipelines
215+
# or set default instrumentation service for all new pipelines
215216
HTML::Pipeline.default_instrumentation_service = service
217+
pipeline = HTML::Pipeline.new [MarkdownFilter], context
218+
pipeline.setup_instrumentation "MarkdownPipeline"
216219
```
217220

218221
Filters are instrumented when they are run through the pipeline. A
@@ -222,15 +225,24 @@ instrumentation call.
222225

223226
``` ruby
224227
service.subscribe "call_filter.html_pipeline" do |event, start, ending, transaction_id, payload|
228+
payload[:pipeline] #=> "MarkdownPipeline", set with `setup_instrumentation`
225229
payload[:filter] #=> "MarkdownFilter"
230+
payload[:context] #=> context Hash
231+
payload[:result] #=> instance of result class
232+
payload[:result][:output] #=> output HTML String or Nokogiri::DocumentFragment
226233
end
227234
```
228235

229236
The full pipeline is also instrumented:
230237

231238
``` ruby
232239
service.subscribe "call_pipeline.html_pipeline" do |event, start, ending, transaction_id, payload|
240+
payload[:pipeline] #=> "MarkdownPipeline", set with `setup_instrumentation`
233241
payload[:filters] #=> ["MarkdownFilter"]
242+
payload[:doc] #=> HTML String or Nokogiri::DocumentFragment
243+
payload[:context] #=> context Hash
244+
payload[:result] #=> instance of result class
245+
payload[:result][:output] #=> output HTML String or Nokogiri::DocumentFragment
234246
end
235247
```
236248

lib/html/pipeline.rb

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ module HTML
2525
# some semblance of type safety.
2626
class Pipeline
2727
autoload :VERSION, 'html/pipeline/version'
28-
autoload :Pipeline, 'html/pipeline/pipeline'
2928
autoload :Filter, 'html/pipeline/filter'
3029
autoload :AbsoluteSourceFilter, 'html/pipeline/absolute_source_filter'
3130
autoload :BodyContent, 'html/pipeline/body_content'
@@ -65,6 +64,12 @@ def self.parse(document_or_html)
6564
# Set an ActiveSupport::Notifications compatible object to enable.
6665
attr_accessor :instrumentation_service
6766

67+
# Public: String name for this Pipeline. Defaults to Class name.
68+
attr_writer :instrumentation_name
69+
def instrumentation_name
70+
@instrumentation_name || self.class.name
71+
end
72+
6873
class << self
6974
# Public: Default instrumentation service for new pipeline objects.
7075
attr_accessor :default_instrumentation_service
@@ -94,7 +99,9 @@ def call(html, context = {}, result = nil)
9499
context = @default_context.merge(context)
95100
context = context.freeze
96101
result ||= @result_class.new
97-
instrument "call_pipeline.html_pipeline", :filters => @filters.map(&:name) do
102+
payload = default_payload :filters => @filters.map(&:name),
103+
:context => context, :result => result
104+
instrument "call_pipeline.html_pipeline", payload do
98105
result[:output] =
99106
@filters.inject(html) do |doc, filter|
100107
perform_filter(filter, doc, context, result)
@@ -109,22 +116,13 @@ def call(html, context = {}, result = nil)
109116
#
110117
# Returns the result of the filter.
111118
def perform_filter(filter, doc, context, result)
112-
instrument "call_filter.html_pipeline", :filter => filter.name do
119+
payload = default_payload :filter => filter.name,
120+
:context => context, :result => result
121+
instrument "call_filter.html_pipeline", payload do
113122
filter.call(doc, context, result)
114123
end
115124
end
116125

117-
# Internal: if the `instrumentation_service` object is set, instruments the
118-
# block, otherwise the block is ran without instrumentation.
119-
#
120-
# Returns the result of the provided block.
121-
def instrument(event, payload = nil)
122-
return yield unless instrumentation_service
123-
instrumentation_service.instrument event, payload do
124-
yield
125-
end
126-
end
127-
128126
# Like call but guarantee the value returned is a DocumentFragment.
129127
# Pipelines may return a DocumentFragment or a String. Callers that need a
130128
# DocumentFragment should use this method.
@@ -143,6 +141,36 @@ def to_html(input, context = {}, result = nil)
143141
output.to_s
144142
end
145143
end
144+
145+
# Public: setup instrumentation for this pipeline.
146+
#
147+
# Returns nothing.
148+
def setup_instrumentation(name = nil, service = nil)
149+
self.instrumentation_name = name
150+
self.instrumentation_service =
151+
service || self.class.default_instrumentation_service
152+
end
153+
154+
# Internal: if the `instrumentation_service` object is set, instruments the
155+
# block, otherwise the block is ran without instrumentation.
156+
#
157+
# Returns the result of the provided block.
158+
def instrument(event, payload = nil)
159+
payload ||= default_payload
160+
return yield(payload) unless instrumentation_service
161+
instrumentation_service.instrument event, payload do |payload|
162+
yield payload
163+
end
164+
end
165+
166+
# Internal: Default payload for instrumentation.
167+
#
168+
# Accepts a Hash of additional payload data to be merged.
169+
#
170+
# Returns a Hash.
171+
def default_payload(payload = {})
172+
{:pipeline => instrumentation_name}.merge(payload)
173+
end
146174
end
147175
end
148176

test/helpers/mocked_instrumentation_service.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ def initialize(event = nil, events = [])
55
subscribe event
66
end
77
def instrument(event, payload = nil)
8-
res = yield
8+
payload ||= {}
9+
res = yield payload
910
events << [event, payload, res] if @subscribe == event
1011
res
1112
end
1213
def subscribe(event)
1314
@subscribe = event
15+
@events
1416
end
1517
end

test/html/pipeline_test.rb

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class HTML::PipelineTest < Test::Unit::TestCase
55
Pipeline = HTML::Pipeline
66
class TestFilter
77
def self.call(input, context, result)
8-
input
8+
input.reverse
99
end
1010
end
1111

@@ -17,24 +17,28 @@ def setup
1717

1818
def test_filter_instrumentation
1919
service = MockedInstrumentationService.new
20-
service.subscribe "call_filter.html_pipeline"
20+
events = service.subscribe "call_filter.html_pipeline"
2121
@pipeline.instrumentation_service = service
22-
filter("hello")
23-
event, payload, res = service.events.pop
22+
filter(body = "hello")
23+
event, payload, res = events.pop
2424
assert event, "event expected"
2525
assert_equal "call_filter.html_pipeline", event
2626
assert_equal TestFilter.name, payload[:filter]
27+
assert_equal @pipeline.class.name, payload[:pipeline]
28+
assert_equal body.reverse, payload[:result][:output]
2729
end
2830

2931
def test_pipeline_instrumentation
3032
service = MockedInstrumentationService.new
31-
service.subscribe "call_pipeline.html_pipeline"
33+
events = service.subscribe "call_pipeline.html_pipeline"
3234
@pipeline.instrumentation_service = service
33-
filter("hello")
34-
event, payload, res = service.events.pop
35+
filter(body = "hello")
36+
event, payload, res = events.pop
3537
assert event, "event expected"
3638
assert_equal "call_pipeline.html_pipeline", event
3739
assert_equal @pipeline.filters.map(&:name), payload[:filters]
40+
assert_equal @pipeline.class.name, payload[:pipeline]
41+
assert_equal body.reverse, payload[:result][:output]
3842
end
3943

4044
def test_default_instrumentation_service
@@ -46,6 +50,24 @@ def test_default_instrumentation_service
4650
Pipeline.default_instrumentation_service = nil
4751
end
4852

53+
def test_setup_instrumentation
54+
assert_nil @pipeline.instrumentation_service
55+
56+
service = MockedInstrumentationService.new
57+
events = service.subscribe "call_pipeline.html_pipeline"
58+
@pipeline.setup_instrumentation name = 'foo', service
59+
60+
assert_equal service, @pipeline.instrumentation_service
61+
assert_equal name, @pipeline.instrumentation_name
62+
63+
filter(body = 'foo')
64+
65+
event, payload, res = events.pop
66+
assert event, "expected event"
67+
assert_equal name, payload[:pipeline]
68+
assert_equal body.reverse, payload[:result][:output]
69+
end
70+
4971
def filter(input)
5072
@pipeline.call(input)
5173
end

0 commit comments

Comments
 (0)