forked from elastic/built-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontributing-patch-plugin.html
844 lines (801 loc) · 34.8 KB
/
contributing-patch-plugin.html
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="UTF-8">
<title>Contributing a patch to a Logstash plugin | Logstash Reference [8.6] | Elastic</title>
<meta class="elastic" name="content" content="Contributing a patch to a Logstash plugin | Logstash Reference [8.6]">
<link rel="home" href="index.html" title="Logstash Reference [8.6]"/>
<link rel="up" href="contributing-to-logstash.html" title="Contributing to Logstash"/>
<link rel="prev" href="plugin-listing.html" title="List your plugin"/>
<link rel="next" href="contribute-to-core.html" title="Extending Logstash core"/>
<meta class="elastic" name="product_version" content="8.6"/>
<meta class="elastic" name="product_name" content="Logstash"/>
<meta class="elastic" name="website_area" content="documentation"/>
<meta name="DC.type" content="Learn/Docs/Logstash/Reference/8.6"/>
<meta name="DC.subject" content="Logstash"/>
<meta name="DC.identifier" content="8.6"/>
<meta name="robots" content="noindex,nofollow"/>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.optimizely.com/js/18132920325.js"></script>
<link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/manifest.json">
<meta name="apple-mobile-web-app-title" content="Elastic">
<meta name="application-name" content="Elastic">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="theme-color" content="#ffffff">
<meta name="naver-site-verification" content="936882c1853b701b3cef3721758d80535413dbfd" />
<meta name="yandex-verification" content="d8a47e95d0972434" />
<meta name="localized" content="true" />
<meta name="st:robots" content="follow,index" />
<meta property="og:image" content="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt280217a63b82a734/6202d3378b1f312528798412/elastic-logo.svg" />
<meta property="og:image:width" content="500" />
<meta property="og:image:height" content="172" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon-precomposed" sizes="64x64" href="/favicon_64x64_16bit.png">
<link rel="apple-touch-icon-precomposed" sizes="32x32" href="/favicon_32x32.png">
<link rel="apple-touch-icon-precomposed" sizes="16x16" href="/favicon_16x16.png">
<!-- Give IE8 a fighting chance -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<link rel="stylesheet" type="text/css" href="/guide/static/styles.css" />
</head>
<!--© 2015-2022 Elasticsearch B.V. -->
<!-- All Elastic documentation is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. -->
<!-- http://creativecommons.org/licenses/by-nc-nd/4.0/ -->
<body>
<!-- Google Tag Manager -->
<script>dataLayer = [];</script><noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-58RLH5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-58RLH5');</script>
<!-- End Google Tag Manager -->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-12395217-16"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-12395217-16');
</script>
<!-- Google Tag Manager for GA4 -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-KNJMG2M');</script>
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-KNJMG2M" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager for GA4-->
<div id='elastic-nav' style="display:none;"></div>
<script src='https://www.elastic.co/elastic-nav.js'></script>
<div class="main-container">
<section id="content" >
<div class="content-wrapper">
<section id="guide" lang="en">
<div class="container-fluid">
<div class="row pb-3">
<div class="col-12 order-2 col-md-4 order-md-1 col-lg-3 h-almost-full-md sticky-top-md" id="left_col">
<!-- The TOC is appended here -->
</div>
<div class="col-12 order-1 col-md-8 order-md-2 col-lg-7 order-lg-2 guide-section" id="middle_col">
<!-- start body -->
<div class="page_header">
<strong>IMPORTANT</strong>: No additional bug fixes or documentation updates
will be released for this version. For the latest information, see the
<a href="../current/index.html">current release documentation</a>.
</div>
<div id="content">
<div class="breadcrumbs">
<span class="breadcrumb-link"><a href="/guide/">Elastic Docs</a></span>
<span class="chevron-right">›</span><span class="breadcrumb-link"><a href="index.html">Logstash Reference [8.6]</a></span>
<span class="chevron-right">›</span><span class="breadcrumb-link"><a href="contributing-to-logstash.html">Contributing to Logstash</a></span>
</div>
<div class="navheader">
<span class="prev">
<a href="plugin-listing.html">« List your plugin</a>
</span>
<span class="next">
<a href="contribute-to-core.html">Extending Logstash core »</a>
</span>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h2 class="title"><a id="contributing-patch-plugin"></a>Contributing a patch to a Logstash plugin</h2>
</div></div></div>
<p>This section discusses the information you need to know to successfully contribute a patch to a Logstash plugin.</p>
<p>Each plugin defines its own configuration options. These control the behavior of the plugin to some degree.
Configuration option definitions commonly include:</p>
<div class="ulist itemizedlist">
<ul class="itemizedlist">
<li class="listitem">
Data validation
</li>
<li class="listitem">
Default value
</li>
<li class="listitem">
Any required flags
</li>
</ul>
</div>
<p>Plugins are subclasses of a Logstash base class. A plugin’s base class defines common configuration and methods.</p>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="contrib-patch-input"></a>Input plugins</h3>
</div></div></div>
<p>Input plugins ingest data from an external source. Input plugins are always associated with a codec. An input plugin
always has an associated codec plugin. Input and codec plugins operate in conjunction to create a Logstash event and add
that event to the processing queue. An input codec is a subclass of the <code class="literal">LogStash::Inputs::Base</code> class.</p>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="input-api"></a>Input API</h4>
</div></div></div>
<div class="informaltable">
<table border="0" cellpadding="4px">
<colgroup>
<col/>
<col/>
</colgroup>
<tbody valign="top">
<tr>
<td valign="top">
<p>
<code class="literal">#register() -> nil</code>
</p>
</td>
<td valign="top">
<p>
Required. This API sets up resources for the plugin, typically the connection to the
external source.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#run(queue) -> nil</code>
</p>
</td>
<td valign="top">
<p>
Required. This API fetches or listens for source data, typically looping until stopped. Must handle
errors inside the loop. Pushes any created events to the queue object specified in the method argument. Some inputs may
receive batched data to minimize the external call overhead.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#stop() -> nil</code>
</p>
</td>
<td valign="top">
<p>
Optional. Stops external connections and cleans up.
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="contrib-patch-codec"></a>Codec plugins</h3>
</div></div></div>
<p>Codec plugins decode input data that has a specific structure, such as JSON input data. A codec plugin is a subclass of
<code class="literal">LogStash::Codecs::Base</code>.</p>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="codec-api"></a>Codec API</h4>
</div></div></div>
<div class="informaltable">
<table border="0" cellpadding="4px">
<colgroup>
<col/>
<col/>
</colgroup>
<tbody valign="top">
<tr>
<td valign="top">
<p>
<code class="literal">#register() -> nil</code>
</p>
</td>
<td valign="top">
<p>
Identical to the API of the same name for input plugins.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#decode(data){|event| block} -> nil</code>
</p>
</td>
<td valign="top">
<p>
Must be implemented. Used to create an Event from the raw data given in the method
argument. Must handle errors. The caller must provide a Ruby block. The block is called with the created Event.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#encode(event) -> nil</code>
</p>
</td>
<td valign="top">
<p>
Required. Used to create a structured data object from the given Event. May handle
errors. This method calls a block that was previously stored as @on_event with two arguments: the original event and the
data object.
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="contrib-patch-filter"></a>Filter plugins</h3>
</div></div></div>
<p>A mechanism to change, mutate or merge one or more Events. A filter plugin is a subclass of the <code class="literal">LogStash::Filters::Base</code>
class.</p>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="filter-api"></a>Filter API</h4>
</div></div></div>
<div class="informaltable">
<table border="0" cellpadding="4px">
<colgroup>
<col/>
<col/>
</colgroup>
<tbody valign="top">
<tr>
<td valign="top">
<p>
<code class="literal">#register() -> nil</code>
</p>
</td>
<td valign="top">
<p>
Identical to the API of the same name for input plugins.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#filter(event) -> nil</code>
</p>
</td>
<td valign="top">
<p>
Required. May handle errors. Used to apply a mutation function to the given event.
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="contrib-patch-output"></a>Output plugins</h3>
</div></div></div>
<p>A mechanism to send an event to an external destination. This process may require serialization. An output plugin is a
subclass of the <code class="literal">LogStash::Outputs::Base</code> class.</p>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="output-api"></a>Output API</h4>
</div></div></div>
<div class="informaltable">
<table border="0" cellpadding="4px">
<colgroup>
<col/>
<col/>
</colgroup>
<tbody valign="top">
<tr>
<td valign="top">
<p>
<code class="literal">#register() -> nil</code>
</p>
</td>
<td valign="top">
<p>
Identical to the API of the same name for input plugins.
</p>
</td>
</tr>
<tr>
<td valign="top">
<p>
<code class="literal">#receive(event) -> nil</code>
</p>
</td>
<td valign="top">
<p>
Required. Must handle errors. Used to prepare the given event for transmission to
the external destination. Some outputs may buffer the prepared events to batch transmit to the destination.
</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="patch-process"></a>Process</h3>
</div></div></div>
<p>A bug or feature is identified. An issue is created in the plugin repository. A patch is created and a pull request (PR)
is submitted. After review and possible rework the PR is merged and the plugin is published.</p>
<p>The <a class="xref" href="community-maintainer.html" title="Logstash Plugins Community Maintainer Guide">Community Maintainer Guide</a> explains, in more detail, the process of getting a patch accepted,
merged and published. The Community Maintainer Guide also details the roles that contributors and maintainers are
expected to perform.</p>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="test-methods"></a>Testing methodologies</h3>
</div></div></div>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="tdd"></a>Test driven development</h4>
</div></div></div>
<p>Test driven development (TDD) describes a methodology for using tests to guide evolution of source code.
For our purposes, we are use only a part of it.
Before writing the fix, we create tests that illustrate the bug by failing.
We stop when we have written enough code to make the tests pass and submit the fix and tests as a patch.
It is not necessary to write the tests before the fix, but it is very easy to write a passing test
afterwards that may not actually verify that the fault is really fixed especially if the fault can be triggered via
multiple execution paths or varying input data.</p>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h4 class="title"><a id="rspec"></a>RSpec framework</h4>
</div></div></div>
<p>Logstash uses Rspec, a Ruby testing framework, to define and run the test suite. What follows is a summary of various
sources.</p>
<p><strong>Rspec Example.</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby"> 2 require "logstash/devutils/rspec/spec_helper"
3 require "logstash/plugin"
4
5 describe "outputs/riemann" do
6 describe "#register" do
7 let(:output) do
8 LogStash::Plugin.lookup("output", "riemann").new(configuration)
9 end
10
11 context "when no protocol is specified" do
12 let(:configuration) { Hash.new }
13
14 it "the method completes without error" do
15 expect {output.register}.not_to raise_error
16 end
17 end
18
19 context "when a bad protocol is specified" do
20 let(:configuration) { {"protocol" => "fake"} }
21
22 it "the method fails with error" do
23 expect {output.register}.to raise_error
24 end
25 end
26
27 context "when the tcp protocol is specified" do
28 let(:configuration) { {"protocol" => "tcp"} }
29
30 it "the method completes without error" do
31 expect {output.register}.not_to raise_error
32 end
33 end
34 end
35
36 describe "#receive" do
37 let(:output) do
38 LogStash::Plugin.lookup("output", "riemann").new(configuration)
39 end
40
41 context "when operating normally" do
42 let(:configuration) { Hash.new }
43 let(:event) do
44 data = {"message"=>"hello", "@version"=>"1",
45 "@timestamp"=>"2015-06-03T23:34:54.076Z",
46 "host"=>"vagrant-ubuntu-trusty-64"}
47 LogStash::Event.new(data)
48 end
49
50 before(:example) do
51 output.register
52 end
53
54 it "should accept the event" do
55 expect { output.receive event }.not_to raise_error
56 end
57 end
58 end
59 end</pre>
</div>
<p><strong>Describe blocks (lines 5, 6 and 36 in Example 1).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">describe(string){block} -> nil
describe(Class){block} -> nil</pre>
</div>
<p>With RSpec, we are always describing the plugin method behavior. The describe block is added in logical sections and can
accept either an existing class name or a string. The string used in line 5 is the plugin name. Line 6 is the register
method, line 36 is the receive method. It is a RSpec convention to prefix instance methods with one hash and class
methods with one dot.</p>
<p><strong>Context blocks (lines 11, 19, 27 and 41).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">context(string){block} -> nil</pre>
</div>
<p>In RSpec, context blocks define sections that group tests by a variation. The string should start with the word <code class="literal">when</code>
and then detail the variation. See line 11. The tests in the content block should should only be for that variation.</p>
<p><strong>Let blocks (lines 7, 12, 20, 28, 37, 42 and 43).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">let(symbol){block} -> nil</pre>
</div>
<p>In RSpec, <code class="literal">let</code> blocks define resources for use in the test blocks. These resources are reinitialized for every test
block. They are available as method calls inside the test block. Define <code class="literal">let</code> blocks in <code class="literal">describe</code> and <code class="literal">context</code> blocks,
which scope the <code class="literal">let</code> block and any other nested blocks.
You can use other <code class="literal">let</code> methods defined later within the <code class="literal">let</code> block body. See lines 7-9, which define the output resource
and use the configuration method, defined with different variations in lines 12, 20 and 28.</p>
<p><strong>Before blocks (line 50).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">before(symbol){block} -> nil - symbol is one of :suite, :context, :example, but :all and :each are synonyms for :suite and :example respectively.</pre>
</div>
<p>In RSpec, <code class="literal">before</code> blocks are used to further set up any resources that would have been initialized in a <code class="literal">let</code> block.
You cannot define <code class="literal">let</code> blocks inside <code class="literal">before</code> blocks.</p>
<p>You can also define <code class="literal">after</code> blocks, which are typically used to clean up any setup activity performed by a <code class="literal">before</code> block.</p>
<p><strong>It blocks (lines 14, 22, 30 and 54).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">it(string){block} -> nil</pre>
</div>
<p>In RSpec, <code class="literal">it</code> blocks set the expectations that verify the behavior of the tested code. The string should not start with
<em>it</em> or <em>should</em>, but needs to express the outcome of the expectation. When put together the texts from the enclosing
describe, <code class="literal">context</code> and <code class="literal">it</code> blocks should form a fairly readable sentence, as in lines 5, 6, 11 and 14:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">outputs/riemann
#register when no protocol is specified the method completes without error</pre>
</div>
<p>Readable code like this make the goals of tests easy to understand.</p>
<p><strong>Expect method (lines 15, 23, 31, 55).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">expect(object){block} -> nil</pre>
</div>
<p>In RSpec, the expect method verifies a statement that compares an actual result to an expected result. The <code class="literal">expect</code> method
is usually paired with a call to the <code class="literal">to</code> or <code class="literal">not_to</code> methods. Use the block form when expecting errors or observing for
changes. The <code class="literal">to</code> or <code class="literal">not_to</code> methods require a <code class="literal">matcher</code> object that encapsulates the expected value. The argument form
of the <code class="literal">expect</code> method encapsulates the actual value. When put together the whole line tests the actual against the
expected value.</p>
<p><strong>Matcher methods (lines 15, 23, 31, 55).</strong></p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">raise_error(error class|nil) -> matcher instance
be(object) -> matcher instance
eq(object) -> matcher instance
eql(object) -> matcher instance
for more see http://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers</pre>
</div>
<p>In RSpec, a matcher is an object generated by the equivalent method call (be, eq) that will be used to evaluate the
expected against the actual values.</p>
</div>
</div>
<div class="section">
<div class="titlepage"><div><div>
<h3 class="title"><a id="all-together"></a>Putting it all together</h3>
</div></div></div>
<p>This example fixes an <a href="https://github.com/logstash-plugins/logstash-output-zeromq/issues/9" class="ulink" target="_top">issue</a> in the ZeroMQ output
plugin. The issue does not require knowledge of ZeroMQ.</p>
<p>The activities in this example have the following prerequisites:</p>
<div class="ulist itemizedlist">
<ul class="itemizedlist">
<li class="listitem">
A minimal knowledge of Git and Github. See the <a href="https://help.github.com/categories/bootcamp/" class="ulink" target="_top">Github boot camp</a>.
</li>
<li class="listitem">
A text editor.
</li>
<li class="listitem">
A JRuby <a href="https://www.ruby-lang.org/en/documentation/installation/#managers" class="ulink" target="_top">runtime</a>
<a href="https://howistart.org/posts/ruby/1" class="ulink" target="_top">environment</a>. The <code class="literal">chruby</code> tool manages Ruby versions.
</li>
<li class="listitem">
JRuby 1.7.22 or later.
</li>
<li class="listitem">
The <code class="literal">bundler</code> and <code class="literal">rake</code> gems installed.
</li>
<li class="listitem">
ZeroMQ <a href="http://zeromq.org/intro:get-the-software" class="ulink" target="_top">installed</a>.
</li>
</ul>
</div>
<div class="olist orderedlist">
<ol class="orderedlist">
<li class="listitem">
In Github, fork the ZeroMQ <a href="https://github.com/logstash-plugins/logstash-output-zeromq" class="ulink" target="_top">output plugin repository</a>.
</li>
<li class="listitem">
On your local machine, <a href="https://help.github.com/articles/fork-a-repo/" class="ulink" target="_top">clone</a> the fork to a known folder such as
<code class="literal">logstash/</code>.
</li>
<li class="listitem">
<p>Open the following files in a text editor:</p>
<div class="ulist itemizedlist">
<ul class="itemizedlist">
<li class="listitem">
<code class="literal">logstash-output-zeromq/lib/logstash/outputs/zeromq.rb</code>
</li>
<li class="listitem">
<code class="literal">logstash-output-zeromq/lib/logstash/util/zeromq.rb</code>
</li>
<li class="listitem">
<code class="literal">logstash-output-zeromq/spec/outputs/zeromq_spec.rb</code>
</li>
</ul>
</div>
</li>
<li class="listitem">
<p>According to the issue, log output in server mode must indicate <code class="literal">bound</code>. Furthermore, the test file contains no tests.</p>
<div class="note admon">
<div class="icon"></div>
<div class="admon_content">
<p>Line 21 of <code class="literal">util/zeromq.rb</code> reads <code class="literal">@logger.info("0mq: #{server? ? 'connected' : 'bound'}", :address => address)</code></p>
</div>
</div>
</li>
<li class="listitem">
<p>In the text editor, require <code class="literal">zeromq.rb</code> for the file <code class="literal">zeromq_spec.rb</code> by adding the following
lines:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">require "logstash/outputs/zeromq"
require "logstash/devutils/rspec/spec_helper"</pre>
</div>
</li>
<li class="listitem">
<p>The desired error message should read:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">LogStash::Outputs::ZeroMQ when in server mode a 'bound' info line is logged</pre>
</div>
<p>To properly generate this message, add a <code class="literal">describe</code> block with the fully qualified class name as the argument, a context
block, and an <code class="literal">it</code> block.</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">describe LogStash::Outputs::ZeroMQ do
context "when in server mode" do
it "a 'bound' info line is logged" do
end
end
end</pre>
</div>
</li>
<li class="listitem">
<p>To add the missing test, use an instance of the ZeroMQ output and a substitute logger. This example uses an RSpec feature
called <em>test doubles</em> as the substitute logger.</p>
<p>Add the following lines to <code class="literal">zeromq_spec.rb</code>, after <code class="literal">describe LogStash::Outputs::ZeroMQ do</code> and before <code class="literal">context "when in
server mode" do</code>:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby"> let(:output) { described_class.new("mode" => "server", "topology" => "pushpull" }
let(:tracer) { double("logger") }</pre>
</div>
</li>
<li class="listitem">
<p>Add the body to the <code class="literal">it</code> block. Add the following five lines after the line <code class="literal">context "when in server mode" do</code>:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby"> allow(tracer).to receive(:debug)<a id="CO45-1"></a><i class="conum" data-value="1"></i>
output.logger = logger<a id="CO45-2"></a><i class="conum" data-value="2"></i>
expect(tracer).to receive(:info).with("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})<a id="CO45-3"></a><i class="conum" data-value="3"></i>
output.register<a id="CO45-4"></a><i class="conum" data-value="4"></i>
output.do_close<a id="CO45-5"></a><i class="conum" data-value="5"></i></pre>
</div>
</li>
</ol>
</div>
<div class="calloutlist">
<table border="0" summary="Callout list">
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO45-1"><i class="conum" data-value="1"></i></a></p>
</td>
<td align="left" valign="top">
<p>Allow the double to receive <code class="literal">debug</code> method calls.</p>
</td>
</tr>
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO45-2"><i class="conum" data-value="2"></i></a></p>
</td>
<td align="left" valign="top">
<p>Make the output use the test double.</p>
</td>
</tr>
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO45-3"><i class="conum" data-value="3"></i></a></p>
</td>
<td align="left" valign="top">
<p>Set an expectation on the test to receive an <code class="literal">info</code> method call.</p>
</td>
</tr>
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO45-4"><i class="conum" data-value="4"></i></a></p>
</td>
<td align="left" valign="top">
<p>Call <code class="literal">register</code> on the output.</p>
</td>
</tr>
<tr>
<td align="left" valign="top" width="5%">
<p><a href="#CO45-5"><i class="conum" data-value="5"></i></a></p>
</td>
<td align="left" valign="top">
<p>Call <code class="literal">do_close</code> on the output so the test does not hang.</p>
</td>
</tr>
</table>
</div>
<p>At the end of the modifications, the relevant code section reads:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">require "logstash/outputs/zeromq"
require "logstash/devutils/rspec/spec_helper"
describe LogStash::Outputs::ZeroMQ do
let(:output) { described_class.new("mode" => "server", "topology" => "pushpull") }
let(:tracer) { double("logger") }
context "when in server mode" do
it "a ‘bound’ info line is logged" do
allow(tracer).to receive(:debug)
output.logger = tracer
expect(tracer).to receive(:info).with("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})
output.register
output.do_close
end
end
end</pre>
</div>
<p>To run this test:</p>
<div class="olist orderedlist">
<ol class="orderedlist">
<li class="listitem">
Open a terminal window
</li>
<li class="listitem">
Navigate to the cloned plugin folder
</li>
<li class="listitem">
The first time you run the test, run the command <code class="literal">bundle install</code>
</li>
<li class="listitem">
Run the command <code class="literal">bundle exec rspec</code>
</li>
</ol>
</div>
<p>Assuming all prerequisites were installed correctly, the test fails with output similar to:</p>
<div class="pre_wrapper lang-shell">
<pre class="programlisting prettyprint lang-shell">Using Accessor#strict_set for specs
Run options: exclude {:redis=>true, :socket=>true, :performance=>true, :couchdb=>true, :elasticsearch=>true,
:elasticsearch_secure=>true, :export_cypher=>true, :integration=>true, :windows=>true}
LogStash::Outputs::ZeroMQ
when in server mode
a ‘bound’ info line is logged (FAILED - 1)
Failures:
1) LogStash::Outputs::ZeroMQ when in server mode a ‘bound’ info line is logged
Failure/Error: output.register
Double "logger" received :info with unexpected arguments
expected: ("0mq: bound", {:address=>"tcp://127.0.0.1:2120"})
got: ("0mq: connected", {:address=>"tcp://127.0.0.1:2120"})
# ./lib/logstash/util/zeromq.rb:21:in `setup'
# ./lib/logstash/outputs/zeromq.rb:92:in `register'
# ./lib/logstash/outputs/zeromq.rb:91:in `register'
# ./spec/outputs/zeromq_spec.rb:13:in `(root)'
# /Users/guy/.gem/jruby/1.9.3/gems/rspec-wait-0.0.7/lib/rspec/wait.rb:46:in `(root)'
Finished in 0.133 seconds (files took 1.28 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/outputs/zeromq_spec.rb:10 # LogStash::Outputs::ZeroMQ when in server mode a ‘bound’ info line is logged
Randomized with seed 2568</pre>
</div>
<p>To correct the error, open the <code class="literal">util/zeromq.rb</code> file in your text editor and swap the positions of the words <code class="literal">connected</code>
and <code class="literal">bound</code> on line 21. Line 21 now reads:</p>
<div class="pre_wrapper lang-ruby">
<pre class="programlisting prettyprint lang-ruby">@logger.info("0mq: #{server? ? 'bound' : 'connected'}", :address => address)</pre>
</div>
<p>Run the test again with the <code class="literal">bundle exec rspec</code> command.</p>
<p>The test passes with output similar to:</p>
<div class="pre_wrapper lang-shell">
<pre class="programlisting prettyprint lang-shell">Using Accessor#strict_set for specs
Run options: exclude {:redis=>true, :socket=>true, :performance=>true, :couchdb=>true, :elasticsearch=>true, :elasticsearch_secure=>true, :export_cypher=>true, :integration=>true, :windows=>true}
LogStash::Outputs::ZeroMQ
when in server mode
a ‘bound’ info line is logged
Finished in 0.114 seconds (files took 1.22 seconds to load)
1 example, 0 failures
Randomized with seed 45887</pre>
</div>
<p><a href="https://help.github.com/articles/fork-a-repo/#next-steps" class="ulink" target="_top">Commit</a> the changes to git and Github.</p>
<p>Your pull request is visible from the <a href="https://github.com/logstash-plugins/logstash-output-zeromq/pulls" class="ulink" target="_top">Pull Requests</a>
section of the original Github repository. The plugin maintainers review your work, suggest changes if necessary, and
merge and publish a new version of the plugin.</p>
</div>
</div>
<div class="navfooter">
<span class="prev">
<a href="plugin-listing.html">« List your plugin</a>
</span>
<span class="next">
<a href="contribute-to-core.html">Extending Logstash core »</a>
</span>
</div>
</div>
<!-- end body -->
</div>
<div class="col-12 order-3 col-lg-2 order-lg-3 h-almost-full-lg sticky-top-lg" id="right_col">
<div id="sticky_content">
<!-- The OTP is appended here -->
<div class="row">
<div class="col-0 col-md-4 col-lg-0" id="bottom_left_col"></div>
<div class="col-12 col-md-8 col-lg-12">
<div id="rtpcontainer">
<div class="mktg-promo" id="most-popular">
<p class="aside-heading">Most Popular</p>
<div class="pb-2">
<p class="media-type">Video</p>
<a href="https://www.elastic.co/webinars/getting-started-elasticsearch?baymax=default&elektra=docs&storm=top-video">
<p class="mb-0">Get Started with Elasticsearch</p>
</a>
</div>
<div class="pb-2">
<p class="media-type">Video</p>
<a href="https://www.elastic.co/webinars/getting-started-kibana?baymax=default&elektra=docs&storm=top-video">
<p class="mb-0">Intro to Kibana</p>
</a>
</div>
<div class="pb-2">
<p class="media-type">Video</p>
<a href="https://www.elastic.co/webinars/introduction-elk-stack?baymax=default&elektra=docs&storm=top-video">
<p class="mb-0">ELK for Logs & Metrics</p>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<div id='elastic-footer'></div>
<script src='https://www.elastic.co/elastic-footer.js'></script>
<!-- Footer Section end-->
</section>
</div>
<script src="/guide/static/jquery.js"></script>
<script type="text/javascript" src="/guide/static/docs.js"></script>
<script type="text/javascript">
window.initial_state = {}</script>
</body>
</html>