libhoney 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ee74ef74255d89969a6fa143e1b9f28aabf3b32
4
+ data.tar.gz: 5f9659817e94933e8138396315092d36196b00c4
5
+ SHA512:
6
+ metadata.gz: 1ef9aba4d5ed49c28741ed995575177dc831972ea756c18392ef42e6c30a468988481543e01dacb4a58582bb280f84a968ba6e834b470b6af5ec42d79a9001ec
7
+ data.tar.gz: a46d2468b7f38ff6fd2c7f934ca913415ff5b4aef8670ab66d70e8e8414269483818d61c043692e000cff1ba4e5ece43fd92e6b9328fd643006419f3641da353
data/.gitignore ADDED
@@ -0,0 +1,55 @@
1
+ *~
2
+ *.gem
3
+ *.rbc
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ # Used by dotenv library to load environment variables.
15
+ # .env
16
+
17
+ ## Specific to RubyMotion:
18
+ .dat*
19
+ .repl_history
20
+ build/
21
+ *.bridgesupport
22
+ build-iPhoneOS/
23
+ build-iPhoneSimulator/
24
+
25
+ ## Specific to RubyMotion (use of CocoaPods):
26
+ #
27
+ # We recommend against adding the Pods directory to your .gitignore. However
28
+ # you should judge for yourself, the pros and cons are mentioned at:
29
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
30
+ #
31
+ # vendor/Pods/
32
+
33
+ ## Documentation cache and generated files:
34
+ /.yardoc/
35
+ /_yardoc/
36
+ /doc/
37
+ /rdoc/
38
+
39
+ ## Environment normalization:
40
+ /.bundle/
41
+ /vendor/bundle
42
+ /lib/bundler/man/
43
+
44
+ # for a library or gem, you might want to ignore these files since the code is
45
+ # intended to run in multiple environments; otherwise, check them in:
46
+ Gemfile.lock
47
+ .ruby-version
48
+ .ruby-gemset
49
+
50
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
51
+ .rvmrc
52
+
53
+ # vim stuff
54
+ *.swp
55
+ *.swo
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
data/CONTRIBUTORS ADDED
@@ -0,0 +1,2 @@
1
+ Tim Spencer <tspencer@hungry.com>
2
+ Chris Toshok <toshok@honeycomb.io>
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ libhoney
2
+ ========
3
+ Ruby gem for sending events to http://honeycomb.io from within your ruby code.
4
+
5
+ [![Build Status](https://travis-ci.com/honeycombio/libhoney-rb.svg?token=pycxQxHKSNdG7LiWg3Nt&branch=master)](https://travis-ci.com/honeycombio/libhoney-rb)
6
+
7
+ ## Summary
8
+
9
+ libhoney is written to ease the process of sending data to Honeycomb from within
10
+ your ruby code.
11
+
12
+ For an overview of how to use a honeycomb library, see our documentation at
13
+ https://honeycomb.io/docs/send-data/sdks/
14
+
15
+ ## Installation
16
+
17
+ To install the stable release:
18
+
19
+ ```
20
+ gem install libhoney
21
+ ```
22
+
23
+ If you're using bundler, you can also reference the git repo and stay on the bleeding age by putting this in your `Gemfile`:
24
+
25
+ ```
26
+ gem 'libhoney', :git => 'http://github.com/honeycombio/libhoney-rb.git'
27
+ ```
28
+
29
+ ## Example Usage
30
+ ```ruby
31
+ require 'libhoney'
32
+
33
+ # create a client instance
34
+ honey = Libhoney::Client.new(:writekey => "your writekey",
35
+ :dataset => "your dataset")
36
+
37
+ # create an event and add fields to it
38
+ event = honey.event
39
+ event.add_field("duration_ms", 153.12)
40
+ event.add_field("method", "get")
41
+ # send the event
42
+ event.send
43
+
44
+ # when all done, call close
45
+ honey.close
46
+ ```
47
+
48
+ You can find a more complete example demonstrating usage in `example/fact.rb`
49
+
50
+ ## Contributions
51
+
52
+ Features, bug fixes and other changes to libhoney are gladly accepted. Please
53
+ open issues or a pull request with your change. Remember to add your name to the
54
+ CONTRIBUTORS file!
55
+
56
+ All contributions will be released under the Apache License 2.0.
57
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rake/testtask"
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ require "yardstick/rake/measurement"
11
+ Yardstick::Rake::Measurement.new do |measurement|
12
+ measurement.output = "measurement/report.txt"
13
+ end
14
+
15
+ require "yardstick/rake/verify"
16
+ Yardstick::Rake::Verify.new do |verify|
17
+ verify.require_exact_threshold = false
18
+ verify.threshold = 55
19
+ end
20
+
21
+ task :default => :test
data/example/fact.rb ADDED
@@ -0,0 +1,57 @@
1
+ lib = File.expand_path("../../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'libhoney'
5
+
6
+ writekey = "7aefa39399a474bd9f414a8e3f8d9691" # replace this with yours from https://ui.honeycomb.com/account
7
+ dataset = "factorial"
8
+
9
+
10
+ def factorial(n)
11
+ return -1 * factorial(abs(n)) if n < 0
12
+ return 1 if n == 0
13
+ return n * factorial(n - 1)
14
+ end
15
+
16
+ def run_fact(low, high, libh_builder)
17
+ for i in low..high do
18
+ ev = libh_builder.event
19
+ ev.metadata = { :fn => "run_fact",
20
+ :i => i }
21
+ ev.with_timer("fact") do
22
+ res = factorial(10 + i)
23
+ ev.add_field("retval", res)
24
+ end
25
+ ev.send
26
+ end
27
+ end
28
+
29
+ def read_responses(resp_queue)
30
+ loop do
31
+ resp = resp_queue.pop()
32
+ break if resp == nil
33
+ puts "sending event with metadata #{resp.metadata} took #{resp.duration*1000}ms and got response code #{resp.status_code}"
34
+ end
35
+ end
36
+
37
+
38
+ libhoney = Libhoney::Client.new(:writekey => writekey,
39
+ :dataset => dataset,
40
+ :max_concurrent_batches => 1)
41
+
42
+ resps = libhoney.responses()
43
+ Thread.new do
44
+ begin
45
+ libhoney.add_field("version", "3.4.5")
46
+ libhoney.add_dynamic_field("num_threads", Proc.new { Thread.list.select {|thread| thread.status == "run"}.count })
47
+ libhoney.send_now({:status => "starting run"})
48
+ run_fact(1, 20, libhoney.builder({:range => "low"}))
49
+ run_fact(31, 40, libhoney.builder({:range => "high"}))
50
+ libhoney.send_now({:status => "ending run"})
51
+ libhoney.close(true)
52
+ rescue Exception => e
53
+ puts e
54
+ end
55
+ end
56
+
57
+ read_responses(resps)
@@ -0,0 +1,112 @@
1
+ require 'libhoney/event'
2
+
3
+ module Libhoney
4
+ class Builder
5
+ attr_accessor :writekey, :dataset, :sample_rate, :api_host
6
+ attr_accessor :fields, :dyn_fields
7
+
8
+ # @api private
9
+ # @see Client#builder
10
+ # @see Builder#builder
11
+ def initialize(libhoney, parent_builder, fields = {}, dyn_fields = {})
12
+ @libhoney = libhoney
13
+
14
+ @fields = {}
15
+ @dyn_fields = {}
16
+
17
+ if parent_builder != nil
18
+ @writekey = parent_builder.writekey
19
+ @dataset = parent_builder.dataset
20
+ @sample_rate = parent_builder.sample_rate
21
+ @api_host = parent_builder.api_host
22
+ @fields.merge!(parent_builder.fields)
23
+ @dyn_fields.merge!(parent_builder.dyn_fields)
24
+ end
25
+
26
+ @fields.merge!(fields)
27
+ @dyn_fields.merge!(dyn_fields)
28
+ self
29
+ end
30
+
31
+ # adds a group of field->values to the events created from this builder.
32
+ #
33
+ # @param data [Hash<String=>any>] field->value mapping.
34
+ # @return [self] this Builder instance.
35
+ # @example using an object
36
+ # honey = Libhoney::Client.new(...)
37
+ # builder = honey.builder
38
+ # builder.add {
39
+ # :responseTime_ms => 100,
40
+ # :httpStatusCode => 200
41
+ # }
42
+ def add(data)
43
+ @fields.merge!(data)
44
+ self
45
+ end
46
+
47
+ # adds a single field->value mapping to the events created from this builder.
48
+ #
49
+ # @param name [string]
50
+ # @param val [any]
51
+ # @return [self] this Builder instance.
52
+ # @example
53
+ # builder.add_field("responseTime_ms", 100)
54
+ def add_field(name, val)
55
+ @fields[name] = val
56
+ self
57
+ end
58
+
59
+ # adds a single field->dynamic value function, which is invoked to supply values when events are created from this builder.
60
+ #
61
+ # @param name [string] the name of the field to add to events.
62
+ # @param fn [#call] the function called to generate the value for this field.
63
+ # @return [self] this Builder instance.
64
+ # @example
65
+ # builder.add_dynamic_field("process_heapUsed", Proc.new { Thread.list.select {|thread| thread.status == "run"}.count })
66
+ def add_dynamic_field(name, fn)
67
+ @dyn_fields[name] = fn
68
+ end
69
+
70
+ # creates and sends an event, including all builder fields/dyn_fields, as well as anything in the optional data parameter.
71
+ #
72
+ # @param data [Hash<String=>any>] optional field->value mapping to add to the event sent.
73
+ # @return [self] this Builder instance
74
+ # @example empty send_now
75
+ # builder.send_now # sends just the data that has been added via add/add_field/add_dynamic_field.
76
+ # @example adding data at send-time
77
+ # builder.send_now {
78
+ # :additionalField => value
79
+ # }
80
+ def send_now(data = {})
81
+ ev = self.event
82
+ ev.add(data)
83
+ ev.send()
84
+ self
85
+ end
86
+
87
+ # creates and returns a new Event containing all fields/dyn_fields from this builder, that can be further fleshed out and sent on its own.
88
+ #
89
+ # @return [Event] an Event instance
90
+ # @example adding data at send-time
91
+ # ev = builder.event
92
+ # ev.addField("additionalField", value)
93
+ # ev.send
94
+ def event
95
+ Event.new(@libhoney, self, @fields, @dyn_fields)
96
+ end
97
+
98
+ # creates and returns a clone of this builder, merged with fields and dyn_fields passed as arguments.
99
+ #
100
+ # @param fields [Hash<String=>any>] a field->value mapping to merge into the new builder.
101
+ # @param dyn_fields [Hash<String=>#call>] a field->dynamic function mapping to merge into the new builder.
102
+ # @return [Builder] a Builder instance
103
+ # @example no additional fields/dyn_field
104
+ # another_builder = the_builder.builder
105
+ # @example additional fields/dyn_field
106
+ # anotherBuilder = the_builder.builder({ :request_id => @request_id },
107
+ # { :active_threads => Proc.new { Thread.list.select {|thread| thread.status == "run"}.count } });
108
+ def builder(fields = {}, dyn_fields = {})
109
+ Builder.new(@libhoney, self, fields, dyn_fields)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,209 @@
1
+ require 'thread'
2
+ require 'time'
3
+ require 'json'
4
+ require 'http'
5
+
6
+ # define a few additions that proxy access through Client's builder. makes Client much tighter.
7
+ class Class
8
+ def builder_attr_accessor(*args)
9
+ args.each do |arg|
10
+ self.class_eval("def #{arg};@builder.#{arg};end")
11
+ self.class_eval("def #{arg}=(val);@builder.#{arg}=val;end")
12
+ end
13
+ end
14
+ def builder_attr_reader(*args)
15
+ args.each do |arg|
16
+ self.class_eval("def #{arg};@builder.#{arg};end")
17
+ end
18
+ end
19
+ def builder_attr_writer(*args)
20
+ args.each do |arg|
21
+ self.class_eval("def #{arg}=(val);@builder.#{arg}=val;end")
22
+ end
23
+ end
24
+ end
25
+
26
+ module Libhoney
27
+ ##
28
+ # This is a library to allow you to send events to Honeycomb from within your
29
+ # ruby application.
30
+ #
31
+ # Example:
32
+ # require 'libhoney'
33
+ # honey = Libhoney.new(writekey, dataset, url, sample_rate, num_workers)
34
+ # event = honey.event
35
+ # event.add({'pglatency' => 100})
36
+ # honey.send(event)
37
+ # <repeat creating and sending events until your program is finished>
38
+ # honey.close
39
+ #
40
+ # Arguments:
41
+ # * *writekey* is the key to use the Honeycomb service
42
+ # * *dataset* is the dataset to write into
43
+ # * *sample_rate* is how many samples you want to keep. IE: 1 means you want 1 out of 1 samples kept, or all of them. 10 means you want 1 out of 10 samples kept. And so on.
44
+ # * *url* is the url to connect to Honeycomb
45
+ # * *num_workers* is the number of threads working on the queue of events you are generating
46
+ #
47
+ # Note that by default, the max queue size is 1000. If the queue gets bigger than that, we start dropping events.
48
+ #
49
+ class Client
50
+ # Instantiates libhoney and prepares it to send events to Honeycomb.
51
+ #
52
+ # @param writekey [String] the write key from your honeycomb team
53
+ # @param dataset [String] the dataset you want
54
+ # @param sample_rate [Fixnum] cause libhoney to send 1 out of sampleRate events. overrides the libhoney instance's value.
55
+ # @param api_host [String] the base url to send events to
56
+ # @param block_on_send [Boolean] if more than pending_work_capacity events are written, block sending further events
57
+ # @param block_on_responses [Boolean] if true, block if there is no thread reading from the response queue
58
+ def initialize(writekey: '',
59
+ dataset: '',
60
+ sample_rate: 1,
61
+ api_host: 'https://api.honeycomb.io/',
62
+ block_on_send: false,
63
+ block_on_responses: false,
64
+ max_batch_size: 50,
65
+ send_frequency: 100,
66
+ max_concurrent_batches: 10,
67
+ pending_work_capacity: 1000)
68
+ # check for insanity
69
+ raise Exception.new('libhoney: max_concurrent_batches must be greater than 0') if max_concurrent_batches < 1
70
+ raise Exception.new('libhoney: sample rate must be greater than 0') if sample_rate < 1
71
+
72
+ raise Exception.new("libhoney: Ruby versions < 2.2 are not supported") if !Gem::Dependency.new("ruby", "~> 2.2").match?("ruby", RUBY_VERSION)
73
+ @builder = Builder.new(self, nil)
74
+ @builder.writekey = writekey
75
+ @builder.dataset = dataset
76
+ @builder.sample_rate = sample_rate
77
+ @builder.api_host = api_host
78
+
79
+ @block_on_send = block_on_send
80
+ @block_on_responses = block_on_responses
81
+ @max_batch_size = max_batch_size
82
+ @send_frequency = send_frequency
83
+ @max_concurrent_batches = max_concurrent_batches
84
+ @pending_work_capacity = pending_work_capacity
85
+ @responses = SizedQueue.new(2 * @pending_work_capacity)
86
+ @tx = nil
87
+ @lock = Mutex.new
88
+
89
+ self
90
+ end
91
+
92
+ builder_attr_accessor :writekey, :dataset, :sample_rate, :api_host
93
+
94
+ attr_reader :block_on_send, :block_on_responses, :max_batch_size,
95
+ :send_frequency, :max_concurrent_batches,
96
+ :pending_work_capacity, :responses
97
+
98
+ def event
99
+ @builder.event
100
+ end
101
+
102
+ def builder(fields = {}, dyn_fields = {})
103
+ @builder.builder(fields, dyn_fields)
104
+ end
105
+
106
+ ##
107
+ # Nuke the queue and wait for inflight requests to complete before returning.
108
+ # If you set drain=true, it will not clear the queue, so all queued requests
109
+ # will drain before exiting.
110
+ def close(drain=false)
111
+ return @tx.close(drain) if @tx
112
+ 0
113
+ end
114
+
115
+ # adds a group of field->values to the global Builder.
116
+ #
117
+ # @param data [Hash<String=>any>] field->value mapping.
118
+ # @return [self] this Client instance
119
+ # @example
120
+ # honey.add {
121
+ # :responseTime_ms => 100,
122
+ # :httpStatusCode => 200
123
+ # }
124
+ def add(data)
125
+ @builder.add(data)
126
+ self
127
+ end
128
+
129
+ # adds a single field->value mapping to the global Builder.
130
+ #
131
+ # @param name [String] name of field to add.
132
+ # @param val [any] value of field to add.
133
+ # @return [self] this Client instance
134
+ # @example
135
+ # honey.addField("responseTime_ms", 100)
136
+ def add_field(name, val)
137
+ @builder.add_field(name, val)
138
+ self
139
+ end
140
+
141
+ # adds a single field->dynamic value function to the global Builder.
142
+ #
143
+ # @param name [String] name of field to add.
144
+ # @param fn [#call] function that will be called to generate the value whenever an event is created.
145
+ # @return [self] this libhoney instance.
146
+ # @example
147
+ # honey.addDynamicField("active_threads", Proc.new { Thread.list.select {|thread| thread.status == "run"}.count })
148
+ def add_dynamic_field(name, fn)
149
+ @builder.add_dynamic_field(name, fn)
150
+ self
151
+ end
152
+
153
+ # creates and sends an event, including all global builder fields/dyn_fields, as well as anything in the optional data parameter.
154
+ #
155
+ # @param data [Hash<String=>any>] optional field->value mapping to add to the event sent.
156
+ # @return [self] this libhoney instance.
157
+ # @example empty sendNow
158
+ # honey.sendNow() # sends just the data that has been added via add/add_field/add_dynamic_field.
159
+ # @example adding data at send-time
160
+ # honey.sendNow {
161
+ # additionalField: value
162
+ # }
163
+ #/
164
+ def send_now(data = {})
165
+ @builder.send_now(data)
166
+ self
167
+ end
168
+
169
+ ##
170
+ # Enqueue an event to send. Sampling happens here, and we will create
171
+ # new threads to handle work as long as we haven't gone over max_concurrent_batches and
172
+ # there are still events in the queue.
173
+ #
174
+ # @param event [Event] the event to send to honeycomb
175
+ # @api private
176
+ def send_event(event)
177
+ @lock.synchronize {
178
+ if !@tx
179
+ @tx = TransmissionClient.new(:max_batch_size => @max_batch_size,
180
+ :send_frequency => @send_frequency,
181
+ :max_concurrent_batches => @max_concurrent_batches,
182
+ :pending_work_capacity => @pending_work_capacity,
183
+ :responses => @responses,
184
+ :block_on_send => @block_on_send,
185
+ :block_on_responses => @block_on_responses)
186
+ end
187
+ }
188
+
189
+ @tx.add(event)
190
+ end
191
+
192
+ # @api private
193
+ def send_dropped_response(event, msg)
194
+ response = Response.new(:error => msg,
195
+ :metadata => event.metadata)
196
+ begin
197
+ @responses.enq(response, !@block_on_responses)
198
+ rescue ThreadError
199
+ # happens if the queue was full and block_on_responses = false.
200
+ end
201
+ end
202
+
203
+ # @api private
204
+ def should_drop(sample_rate)
205
+ rand(1..sample_rate) != 1
206
+ end
207
+
208
+ end
209
+ end
@@ -0,0 +1,102 @@
1
+ module Libhoney
2
+ ##
3
+ # This is the event object that you can fill up with data.
4
+ # The data itself is a ruby hash.
5
+ class Event
6
+ attr_accessor :writekey, :dataset, :sample_rate, :api_host
7
+ attr_accessor :timestamp, :metadata
8
+
9
+ attr_reader :data
10
+
11
+ # @api private
12
+ # @see Client#event
13
+ # @see Builder#event
14
+ def initialize(libhoney, builder, fields = {}, dyn_fields = {})
15
+ @libhoney = libhoney
16
+
17
+ @writekey = builder.writekey
18
+ @dataset = builder.dataset
19
+ @sample_rate = builder.sample_rate
20
+ @api_host = builder.api_host
21
+ @timestamp = Time.now
22
+ @metadata = nil
23
+
24
+ @data = { }
25
+ fields.each { |k, v| self.add_field(k, v) }
26
+ dyn_fields.each { |k, v| self.add_field(k, v.call) }
27
+
28
+ self
29
+ end
30
+
31
+ # adds a group of field->values to this event.
32
+ #
33
+ # @param newdata [Hash<String=>any>] field->value mapping.
34
+ # @return [self] this event.
35
+ # @example using an object
36
+ # builder.event
37
+ # .add({
38
+ # :responseTime_ms => 100,
39
+ # :httpStatusCode => 200
40
+ # })
41
+ def add(newdata)
42
+ @data.merge!(newdata)
43
+ self
44
+ end
45
+
46
+ # adds a single field->value mapping to this event.
47
+ #
48
+ # @param name [String]
49
+ # @param val [any]
50
+ # @return [self] this event.
51
+ # @example
52
+ # builder.event
53
+ # .add_field("responseTime_ms", 100)
54
+ # .send
55
+ def add_field(name, val)
56
+ @data[name] = val
57
+ self
58
+ end
59
+
60
+ # times the execution of a block and adds a field containing the duration in milliseconds
61
+ #
62
+ # @param name [String] the name of the field to add to the event
63
+ # @return [self] this event.
64
+ # @example
65
+ # event.with_timer "task_ms" do
66
+ # # something time consuming
67
+ # end
68
+ def with_timer(name, &block)
69
+ start = Time.now
70
+ block.call
71
+ duration = Time.now - start
72
+ # report in ms
73
+ self.add_field(name, duration * 1000)
74
+ self
75
+ end
76
+
77
+ # sends this event to honeycomb
78
+ #
79
+ # @return [self] this event.
80
+ def send
81
+ # discard if sampling rate says so
82
+ if @libhoney.should_drop(self.sample_rate)
83
+ @libhoney.send_dropped_response(self, "event dropped due to sampling")
84
+ end
85
+
86
+ self.send_presampled()
87
+ end
88
+
89
+ # sends a presampled event to honeycomb
90
+ #
91
+ # @return [self] this event.
92
+ def send_presampled
93
+ raise ArgumentError.new("No metrics added to event. Won't send empty event.") if self.data.length == 0
94
+ raise ArgumentError.new("No APIHost for Honeycomb. Can't send to the Great Unknown.") if self.api_host == ""
95
+ raise ArgumentError.new("No WriteKey specified. Can't send event.") if self.writekey == ""
96
+ raise ArgumentError.new("No Dataset for Honeycomb. Can't send datasetless.") if self.dataset == ""
97
+
98
+ @libhoney.send_event(self)
99
+ self
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Libhoney
3
+ class Response
4
+ attr_accessor :duration, :status_code, :metadata, :error
5
+
6
+ def initialize(duration: 0,
7
+ status_code: 0,
8
+ metadata: nil,
9
+ error: nil)
10
+ @duration = duration
11
+ @status_code = status_code
12
+ @metadata = metadata
13
+ @error = error
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,86 @@
1
+ require 'libhoney/response'
2
+
3
+ module Libhoney
4
+ # @api private
5
+ class TransmissionClient
6
+ def initialize(max_batch_size: 0,
7
+ send_frequency: 0,
8
+ max_concurrent_batches: 0,
9
+ pending_work_capacity: 0,
10
+ responses: 0,
11
+ block_on_send: 0,
12
+ block_on_responses: 0)
13
+
14
+ @responses = responses
15
+ @block_on_send = block_on_send
16
+ @block_on_responses = block_on_responses
17
+ @max_batch_size = max_batch_size
18
+ @send_frequency = send_frequency
19
+ @max_concurrent_batches = max_concurrent_batches
20
+ @pending_work_capacity = pending_work_capacity
21
+
22
+ # use a SizedQueue so the producer will block on adding to the send_queue when @block_on_send is true
23
+ @send_queue = SizedQueue.new(@pending_work_capacity)
24
+ @threads = []
25
+ @lock = Mutex.new
26
+ end
27
+
28
+ def add(event)
29
+ begin
30
+ @send_queue.enq(event, !@block_on_send)
31
+ rescue ThreadError
32
+ # happens if the queue was full and block_on_send = false.
33
+ end
34
+
35
+ @lock.synchronize {
36
+ return if @threads.length > 0
37
+ while @threads.length < @max_concurrent_batches
38
+ @threads << Thread.new { self.send_loop }
39
+ end
40
+ }
41
+ end
42
+
43
+ def send_loop
44
+ # eat events until we run out
45
+ loop {
46
+ e = @send_queue.pop
47
+ break if e == nil
48
+
49
+ before = Time.now
50
+ resp = HTTP.headers('User-Agent' => "libhoney-rb/#{VERSION}",
51
+ 'Content-Type' => 'application/json',
52
+ 'X-Honeycomb-Team' => e.writekey,
53
+ 'X-Honeycomb-SampleRate' => e.sample_rate,
54
+ 'X-Event-Time' => e.timestamp.iso8601)
55
+ .post(URI.join(e.api_host, '/1/events/', e.dataset), :json => e.data)
56
+ after = Time.now
57
+
58
+ response = Response.new(:duration => after - before,
59
+ :status_code => resp.status,
60
+ :metadata => e.metadata)
61
+ begin
62
+ @responses.enq(response, !@block_on_responses)
63
+ rescue ThreadError
64
+ # happens if the queue was full and block_on_send = false.
65
+ end
66
+ }
67
+ end
68
+
69
+ def close(drain)
70
+ # if drain is false, clear the remaining unprocessed events from the queue
71
+ @send_queue.clear if drain == false
72
+
73
+ # send @threads.length number of nils so each thread will fall out of send_loop
74
+ @threads.length.times { @send_queue << nil }
75
+
76
+ @threads.each do |t|
77
+ t.join
78
+ end
79
+ @threads = []
80
+
81
+ @responses.enq(nil)
82
+
83
+ 0
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module Libhoney
2
+ VERSION = "1.0.0"
3
+ end
data/lib/libhoney.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'libhoney/client'
2
+ require 'libhoney/version'
3
+ require 'libhoney/builder'
4
+ require 'libhoney/response'
5
+ require 'libhoney/transmission'
data/libhoney.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "libhoney/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'libhoney'
7
+ spec.version = Libhoney::VERSION
8
+ spec.date = '2016-11-07'
9
+
10
+ spec.summary = "send data to Honeycomb"
11
+ spec.description = "Ruby gem for sending data to Honeycomb"
12
+ spec.authors = ['The Honeycomb.io Team']
13
+ spec.email = 'support@honeycomb.io'
14
+ spec.files = ['lib/libhoney.rb', 'lib/libhoney/version.rb']
15
+ spec.homepage = 'https://github.com/honeycombio/libhoney-rb'
16
+ spec.license = 'Apache-2.0'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.required_ruby_version = '>= 2.2.0'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.7"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "webmock", "~> 2.1"
30
+ spec.add_development_dependency "minitest", "~> 5.0"
31
+ spec.add_development_dependency "yardstick", "~> 0.9"
32
+ spec.add_dependency "http", "~> 2.0"
33
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libhoney
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - The Honeycomb.io Team
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: webmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yardstick
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: http
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ description: Ruby gem for sending data to Honeycomb
98
+ email: support@honeycomb.io
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - ".travis.yml"
105
+ - CONTRIBUTORS
106
+ - Gemfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - example/fact.rb
111
+ - lib/libhoney.rb
112
+ - lib/libhoney/builder.rb
113
+ - lib/libhoney/client.rb
114
+ - lib/libhoney/event.rb
115
+ - lib/libhoney/response.rb
116
+ - lib/libhoney/transmission.rb
117
+ - lib/libhoney/version.rb
118
+ - libhoney.gemspec
119
+ homepage: https://github.com/honeycombio/libhoney-rb
120
+ licenses:
121
+ - Apache-2.0
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.2.0
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.4.5
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: send data to Honeycomb
143
+ test_files: []