honeycomb-beeline 1.0.0.pre.alpha → 1.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ # Honeycomb Beeline for Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/honeycombio/beeline-ruby.svg?branch=master)](https://travis-ci.org/honeycombio/beeline-ruby)
4
+ [![Gem Version](https://badge.fury.io/rb/honeycomb-beeline.svg)](https://badge.fury.io/rb/honeycomb-beeline)
5
+
6
+ This package makes it easy to instrument your Ruby web app to send useful events to [Honeycomb](https://www.honeycomb.io), a service for debugging your software in production.
7
+ - [Usage and Examples](https://docs.honeycomb.io/getting-data-in/beelines/ruby-beeline/)
8
+
9
+ Sign up for a [Honeycomb
10
+ trial](https://ui.honeycomb.io/signup) to obtain an API key before starting.
11
+
12
+ ## Compatible with
13
+
14
+ Requires Ruby version 2.2 or later
15
+
16
+ Built in instrumentation for:
17
+
18
+ - Active Support
19
+ - Faraday
20
+ - Rack
21
+ - Rails
22
+ - Sequel
23
+ - Sinatra
24
+
25
+ ## Get in touch
26
+
27
+ Please reach out to [support@honeycomb.io](mailto:support@honeycomb.io) or ping
28
+ us with the chat bubble on [our website](https://www.honeycomb.io) for any
29
+ assistance. We also welcome [bug reports](https://github.com/honeycombio/beeline-ruby/issues).
30
+
31
+ ## Contributions
32
+
33
+ Features, bug fixes and other changes to `beeline-ruby` are gladly accepted. Please
34
+ open issues or a pull request with your change. Remember to add your name to the
35
+ CONTRIBUTORS file!
36
+
37
+ All contributions will be released under the Apache License 2.0.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+ require "appraisal"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ RuboCop::RakeTask.new(:rubocop)
11
+
12
+ task test: :spec
13
+
14
+ task default: %i[rubocop test]
15
+
16
+ !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"] && task(default: :appraisal)
@@ -0,0 +1,82 @@
1
+ # Upgrade Guide
2
+
3
+ ## 0.8.0 - 1.0.0
4
+
5
+ 1. If you have a web application, remove beeline configuration from the `config.ru` file
6
+ 1. If you have a rails application, run the honeycomb generator `bundle exec rails generate honeycomb {writekey}`
7
+ 1. Replace call to `Honeycomb.init` with the following (if using rails, this will now live in `config/initializers/honeycomb.rb`)
8
+ ```ruby
9
+ Honeycomb.configure do |config|
10
+ config.write_key = "{writekey}"
11
+ config.dataset = "rails"
12
+ end
13
+ ```
14
+ 1. Replace any `Rack::Honeycomb.add_field` calls with the following
15
+ ```ruby
16
+ Honeycomb.client.add_field("name", "value")
17
+ ```
18
+ 1. Replace any `Honeycomb.span` calls with the following
19
+ ```ruby
20
+ Honeycomb.start_span(name: "interesting") do |span|
21
+ span.add_field("name", "value")
22
+ end
23
+ ```
24
+
25
+ ## honeycomb-rails to beeline-ruby
26
+
27
+ 1. Update Gemfile, remove `honeycomb-rails` and add `beeline-ruby`
28
+ 1. Run `bundle install`
29
+ 1. Remove the `honeycomb.rb` initializer from `config/initializers`
30
+ 1. Add the following to the `config.ru` file
31
+ ```ruby
32
+ # config.ru
33
+ require 'honeycomb-beeline'
34
+
35
+ Honeycomb.init(writekey: 'YOUR_API_KEY', dataset: 'YOUR_DATASET')
36
+
37
+ # these next two lines should already exist in some form in this file, it's important to init the honeycomb library before this
38
+ require ::File.expand_path('../config/environment', __FILE__)
39
+ run Rails.application
40
+ ```
41
+ 1. You can use the same write key and dataset from the honeycomb initialiser above, note: the honeycomb-beeline only supports sending events to one dataset. This is due to the fact that the new beeline will include traces for your application by default and these are only viewable from within the same dataset
42
+ 1. Replace any `honeycomb_metadata` calls in your controllers like the following
43
+ ```ruby
44
+ def index
45
+ @bees = Bee.all
46
+ Rack::Honeycomb.add_field(request.env, :bees_count, @bees.count)
47
+ # honeycomb_metadata[:bees_count] = @bees.count
48
+ end
49
+ ```
50
+ 1. If you are manually using the libhoney client as well, it is suggested that you remove the usages of it and rely on the beeline.
51
+ 1. Instrument interesting calls using the new `span` API as per the example below
52
+ ```ruby
53
+ class HomeController < ApplicationController
54
+ def index
55
+ Honeycomb.span do
56
+ @interesting_information = perform_intensive_calculations(params[:honey])
57
+ end
58
+ end
59
+ end
60
+ ```
61
+ 1. `honeycomb-rails` had the ability to automatically populate user information onto your events. Unfortunately `beeline-ruby` does not support this out of the box. You can use something like this snippet below to continue populating this (example for Devise)
62
+ ```ruby
63
+ class ApplicationController < ActionController::Base
64
+ before_action do
65
+ Rack::Honeycomb.add_field(request.env, "user.id", current_user.id)
66
+ Rack::Honeycomb.add_field(request.env, "user.email", current_user.email)
67
+ end
68
+ end
69
+ ```
70
+ 1. (Optional) If you are using `Sequel` for database access there are some additional steps to configure
71
+ ```ruby
72
+ # config.ru
73
+ require 'honeycomb-beeline'
74
+ require 'sequel-honeycomb/auto_install'
75
+
76
+ Honeycomb.init(writekey: 'YOUR_API_KEY', dataset: 'YOUR_DATASET')
77
+ Sequel::Honeycomb::AutoInstall.auto_install!(honeycomb_client: Honeycomb.client, logger: Honeycomb.logger)
78
+
79
+ # these next two lines should already exist in some form in this file, it's important to init the honeycomb library before this
80
+ require ::File.expand_path('../config/environment', __FILE__)
81
+ run Rails.application
82
+ ```
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "honeycomb/beeline"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -uex
4
+
5
+ if [[ "$BUNDLE_GEMFILE" =~ (rails_41.gemfile|rails_42.gemfile)$ ]]; then
6
+ if [[ "$TRAVIS_RUBY_VERSION" =~ ^(2.3)$ ]]; then
7
+ gem uninstall -v '>= 2' -i $(rvm gemdir) -ax bundler
8
+ elif [[ "$TRAVIS_RUBY_VERSION" =~ ^(2.4|2.5)$ ]]; then
9
+ gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler
10
+ fi
11
+ gem install bundler -v '< 2'
12
+ fi
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "honeycomb/beeline/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = Honeycomb::Beeline::NAME
9
+ spec.version = Honeycomb::Beeline::VERSION
10
+ spec.authors = ["Martin Holman"]
11
+ spec.email = ["martin@honeycomb.io"]
12
+
13
+ spec.summary = "Instrument your Ruby apps with Honeycomb"
14
+ spec.homepage = "https://honeycomb.io"
15
+
16
+ spec.license = 'Apache-2.0'
17
+
18
+ spec.required_ruby_version = ">= 2.2.0"
19
+
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata["homepage_uri"] = spec.homepage
22
+ spec.metadata["source_code_uri"] = "https://github.com/honeycombio/beeline-ruby"
23
+ else
24
+ raise "RubyGems 2.0 or newer is required to protect against " \
25
+ "public gem pushes."
26
+ end
27
+
28
+ # Specify which files should be added to the gem when it is released.
29
+ # The `git ls-files -z` loads the files in the RubyGem that have been added
30
+ # into git.
31
+ spec.files = Dir.chdir(File.dirname(__FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject do |file|
33
+ file.match(%r{^(test|spec|features|examples|gemfiles)/})
34
+ end
35
+ end
36
+ spec.bindir = "exe"
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ["lib"]
39
+
40
+ spec.add_dependency "libhoney", "~> 1.8"
41
+
42
+ spec.add_development_dependency "appraisal"
43
+ spec.add_development_dependency "bump"
44
+ spec.add_development_dependency "bundler"
45
+ spec.add_development_dependency "overcommit", "~> 0.46.0"
46
+ spec.add_development_dependency "pry-byebug"
47
+ spec.add_development_dependency "rake"
48
+ spec.add_development_dependency "rspec", "~> 3.0"
49
+ spec.add_development_dependency "rubocop", "< 0.69"
50
+ spec.add_development_dependency "rubocop-performance", "< 1.3.0"
51
+ spec.add_development_dependency "simplecov"
52
+ spec.add_development_dependency "simplecov-console"
53
+ spec.add_development_dependency "webmock"
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ ##
6
+ # Generates an intializer for configuring the Honeycomb beeline
7
+ #
8
+ class HoneycombGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ argument :write_key, required: true, desc: "required"
12
+
13
+ gem "honeycomb-beeline"
14
+
15
+ desc "Configures honeycomb with your write key"
16
+
17
+ def create_initializer_file
18
+ initializer "honeycomb.rb" do
19
+ <<-RUBY.strip_heredoc
20
+ Honeycomb.configure do |config|
21
+ config.write_key = #{write_key.inspect}
22
+ config.dataset = "rails"
23
+ config.notification_events = %w[
24
+ sql.active_record
25
+ render_template.action_view
26
+ render_partial.action_view
27
+ render_collection.action_view
28
+ process_action.action_controller
29
+ send_file.action_controller
30
+ send_data.action_controller
31
+ deliver.action_mailer
32
+ ].freeze
33
+ end
34
+ RUBY
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "libhoney"
4
+
5
+ require "honeycomb/beeline/version"
6
+ require "honeycomb/client"
7
+ require "honeycomb/trace"
8
+
9
+ # main module
10
+ module Honeycomb
11
+ class << self
12
+ attr_reader :client
13
+
14
+ def configure
15
+ Configuration.new.tap do |config|
16
+ yield config
17
+ @client = Honeycomb::Client.new(client: config.client,
18
+ service_name: config.service_name)
19
+ config.after_initialize(@client)
20
+ end
21
+
22
+ @client
23
+ end
24
+
25
+ def start_span(name:, &block)
26
+ client.start_span(name: name, &block)
27
+ end
28
+
29
+ def load_integrations
30
+ %i[faraday rack sinatra rails sequel active_support].each do |integration|
31
+ begin
32
+ require "honeycomb/integrations/#{integration}"
33
+ rescue LoadError
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Used to configure the Honeycomb client
40
+ class Configuration
41
+ attr_accessor :write_key,
42
+ :dataset,
43
+ :api_host
44
+
45
+ attr_writer :service_name, :client
46
+
47
+ def initialize
48
+ @write_key = ENV["HONEYCOMB_WRITEKEY"]
49
+ @dataset = ENV["HONEYCOMB_DATASET"]
50
+ @service_name = ENV["HONEYCOMB_SERVICE"]
51
+ @client = nil
52
+ end
53
+
54
+ def service_name
55
+ @service_name || dataset
56
+ end
57
+
58
+ def client
59
+ options = {}.tap do |o|
60
+ o[:writekey] = write_key
61
+ o[:dataset] = dataset
62
+ api_host && o[:api_host] = api_host
63
+ end
64
+
65
+ @client || Libhoney::Client.new(options)
66
+ end
67
+
68
+ def after_initialize(client)
69
+ super(client) if defined?(super)
70
+ end
71
+ end
72
+ end
73
+
74
+ Honeycomb.load_integrations unless ENV["HONEYCOMB_DISABLE_AUTOCONFIGURE"]
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeycomb
4
+ module Beeline
5
+ NAME = "honeycomb-beeline".freeze
6
+ VERSION = "1.0.0-alpha1".freeze
7
+ USER_AGENT_SUFFIX = "#{NAME}/#{VERSION}".freeze
8
+ end
9
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "forwardable"
5
+ require "honeycomb/beeline/version"
6
+ require "honeycomb/context"
7
+
8
+ module Honeycomb
9
+ # The Honeycomb Beeline client
10
+ class Client
11
+ extend Forwardable
12
+
13
+ def initialize(client:, service_name: nil)
14
+ # attempt to set the user_agent_addition, this will only work if the
15
+ # client has not sent an event prior to being passed in here. This should
16
+ # be most cases
17
+ client.instance_variable_set(:@user_agent_addition,
18
+ Honeycomb::Beeline::USER_AGENT_SUFFIX)
19
+ client.add_field "meta.beeline_version", Honeycomb::Beeline::VERSION
20
+ client.add_field "meta.local_hostname", host_name
21
+
22
+ # maybe make `service_name` a required parameter
23
+ client.add_field "service_name", service_name
24
+ @client = client
25
+ @context = Context.new
26
+
27
+ at_exit do
28
+ client.close
29
+ end
30
+ end
31
+
32
+ def start_span(name:, serialized_trace: nil)
33
+ if context.current_trace.nil?
34
+ Trace.new(serialized_trace: serialized_trace,
35
+ builder: client.builder,
36
+ context: context)
37
+ else
38
+ context.current_span.create_child
39
+ end
40
+
41
+ context.current_span.add_field("name", name)
42
+
43
+ if block_given?
44
+ begin
45
+ yield context.current_span
46
+ rescue StandardError => e
47
+ context.current_span.add_field("request.error", e.class.name)
48
+ context.current_span.add_field("request.error_detail", e.message)
49
+ raise e
50
+ ensure
51
+ context.current_span.send
52
+ end
53
+ else
54
+ context.current_span
55
+ end
56
+ end
57
+
58
+ def add_field(key, value)
59
+ return if context.current_span.nil?
60
+
61
+ context.current_span.add_field("app.#{key}", value)
62
+ end
63
+
64
+ def add_field_to_trace(key, value)
65
+ return if context.current_span.nil?
66
+
67
+ context.current_span.trace.add_field("app.#{key}", value)
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :client, :context
73
+
74
+ def host_name
75
+ # Send the heroku dyno name instead of hostname if available
76
+ ENV["DYNO"] || Socket.gethostname
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Honeycomb
4
+ # Stores the current span and trace context
5
+ class Context
6
+ def current_trace
7
+ return if current_span.nil?
8
+
9
+ current_span.trace
10
+ end
11
+
12
+ def current_span
13
+ spans.last
14
+ end
15
+
16
+ def current_span=(span)
17
+ spans << span
18
+ end
19
+
20
+ def span_sent(span)
21
+ spans.last != span && raise(ArgumentError, "Incorrect span sent")
22
+
23
+ spans.pop
24
+ end
25
+
26
+ private
27
+
28
+ def spans
29
+ storage["spans"] ||= []
30
+ end
31
+
32
+ def storage
33
+ Thread.current[thread_key] ||= {}
34
+ end
35
+
36
+ def thread_key
37
+ @thread_key ||= ["honeycomb", self.class.name, object_id].join("-")
38
+ end
39
+ end
40
+ end