zuora_observability 0.1.0.pre.a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +26 -0
- data/Rakefile +34 -0
- data/app/assets/config/zuora_observability_manifest.js +1 -0
- data/app/assets/stylesheets/zuora_observability/application.css +15 -0
- data/app/controllers/zuora_observability/application_controller.rb +5 -0
- data/app/controllers/zuora_observability/metrics_controller.rb +18 -0
- data/app/helpers/zuora_observability/application_helper.rb +4 -0
- data/app/jobs/zuora_observability/application_job.rb +4 -0
- data/app/mailers/zuora_observability/application_mailer.rb +6 -0
- data/app/models/zuora_observability/application_record.rb +5 -0
- data/app/views/layouts/zuora_observability/application.html.erb +15 -0
- data/config/routes.rb +5 -0
- data/lib/tasks/zuora_observability_tasks.rake +6 -0
- data/lib/zuora_observability.rb +30 -0
- data/lib/zuora_observability/configuration.rb +16 -0
- data/lib/zuora_observability/engine.rb +75 -0
- data/lib/zuora_observability/env.rb +33 -0
- data/lib/zuora_observability/logger.rb +71 -0
- data/lib/zuora_observability/logging/formatter.rb +46 -0
- data/lib/zuora_observability/metrics.rb +79 -0
- data/lib/zuora_observability/metrics/point_value.rb +84 -0
- data/lib/zuora_observability/metrics/telegraf.rb +98 -0
- data/lib/zuora_observability/version.rb +5 -0
- metadata +291 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c7e3a6c154d518f30f5a95e1be272b96b1853b46df0300d4fa45ca8e17f04451
|
4
|
+
data.tar.gz: 0c510e0da92d8b7139586e60bd048bc91f18cc61a9cfe8165f4f44676c223180
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3948b6dacece27853e9df426c81d6f475c1b8b5417131c4318eeac8b88c8ffa4c4f625036f95cc5b73ed78ed53352f182c2f7efb16800e313b797a7416372b3b
|
7
|
+
data.tar.gz: f67b67d6bbbf2ff33c30433f32f321bf28109540bbf54250764509332de9caba0dbcf94917439882ea7a0f8d6e1bc437eae221134ceb4adfac00933dddadd1f6
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Hartley McGuire
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Zuora Observability
|
2
|
+
|
3
|
+
A ruby gem to enable observability into rails applications
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
How to use my plugin.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'zuora_observability'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
```bash
|
17
|
+
$ bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
```bash
|
22
|
+
$ gem install zuora_observability
|
23
|
+
```
|
24
|
+
|
25
|
+
## License
|
26
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
13
|
+
rdoc.title = 'ZuoraObservability'
|
14
|
+
rdoc.options << '--line-numbers'
|
15
|
+
rdoc.rdoc_files.include('README.md')
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
17
|
+
end
|
18
|
+
|
19
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
20
|
+
load 'rails/tasks/engine.rake'
|
21
|
+
|
22
|
+
load 'rails/tasks/statistics.rake'
|
23
|
+
|
24
|
+
require 'bundler/gem_tasks'
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
29
|
+
t.libs << 'test'
|
30
|
+
t.pattern = 'test/**/*_test.rb'
|
31
|
+
t.verbose = false
|
32
|
+
end
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/zuora_observability .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require vs. require_dependency for namespaced controllers
|
4
|
+
# https://github.com/rails/rails/commit/29d17d3ab65633695babc9123463c78248e41a67
|
5
|
+
require_dependency 'zuora_observability/application_controller'
|
6
|
+
|
7
|
+
module ZuoraObservability
|
8
|
+
class MetricsController < ApplicationController
|
9
|
+
# GET /connect/internal/data
|
10
|
+
def metrics
|
11
|
+
if params[:type] == 'stats'
|
12
|
+
render json: Metrics.resque, status: :ok
|
13
|
+
else
|
14
|
+
render json: Metrics.versions, status: :ok
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Zuora observability</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "zuora_observability/application", media: "all" %>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
|
12
|
+
<%= yield %>
|
13
|
+
|
14
|
+
</body>
|
15
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zuora_observability/configuration'
|
4
|
+
require 'zuora_observability/engine'
|
5
|
+
require 'zuora_observability/env'
|
6
|
+
require 'zuora_observability/metrics'
|
7
|
+
require 'zuora_observability/logger'
|
8
|
+
|
9
|
+
require 'zuora_observability/logging/formatter'
|
10
|
+
require 'zuora_observability/metrics/telegraf'
|
11
|
+
require 'zuora_observability/metrics/point_value'
|
12
|
+
|
13
|
+
# Provides Rails application with tools for observabilty
|
14
|
+
module ZuoraObservability
|
15
|
+
class << self
|
16
|
+
attr_writer :configuration
|
17
|
+
|
18
|
+
def configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset
|
27
|
+
@configuration = Configuration.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZuoraObservability
|
4
|
+
# Global configuration that can be set in a Rails initializer
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :enable_metrics, :telegraf_endpoint, :telegraf_debug,
|
7
|
+
:json_logging
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@enable_metrics = false
|
11
|
+
@telegraf_endpoint = 'udp://telegraf-app-metrics.monitoring.svc.cluster.local:8094'
|
12
|
+
@telegraf_debug = false
|
13
|
+
@json_logging = false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZuoraObservability
|
4
|
+
# The ZuoraObservability Engine is mounted to hook into Rails
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace ZuoraObservability
|
7
|
+
|
8
|
+
config.generators do |g|
|
9
|
+
g.test_framework :rspec
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer(:rails_stdout_logging, before: :initialize_logger) do
|
13
|
+
require 'lograge'
|
14
|
+
|
15
|
+
Rails.configuration.logger = ZuoraObservability::Logger.custom_logger(name: "Rails")
|
16
|
+
if !Rails.env.test? && !Rails.env.development?
|
17
|
+
Rails.configuration.lograge.enabled = true
|
18
|
+
Rails.configuration.colorize_logging = false
|
19
|
+
end
|
20
|
+
|
21
|
+
if Rails.configuration.lograge.enabled
|
22
|
+
if Rails.configuration.logger.class.to_s == 'Ougai::Logger'
|
23
|
+
Rails.configuration.lograge.formatter = Class.new do |fmt|
|
24
|
+
def fmt.call(data)
|
25
|
+
{ msg: 'Rails Request', request: data }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
#Rails.configuration.lograge.formatter = Lograge::Formatters::Json.new
|
30
|
+
Rails.configuration.lograge.custom_options = lambda do |event|
|
31
|
+
exceptions = %w(controller action format)
|
32
|
+
items = {
|
33
|
+
#time: event.time.strftime('%FT%T.%6N'),
|
34
|
+
params: event.payload[:params].as_json(except: exceptions).to_json.to_s
|
35
|
+
}
|
36
|
+
items.merge!({exception_object: event.payload[:exception_object]}) if event.payload[:exception_object].present?
|
37
|
+
items.merge!({exception: event.payload[:exception]}) if event.payload[:exception].present?
|
38
|
+
|
39
|
+
if event.payload[:headers].present?
|
40
|
+
# By convertion, headers usually do not have dots. Nginx even rejects headers with dots
|
41
|
+
# All Rails headers are namespaced, like 'rack.input'.
|
42
|
+
# Thus, we can obtain the client headers by rejecting dots
|
43
|
+
request_headers =
|
44
|
+
event.payload[:headers].env.
|
45
|
+
reject { |key| key.to_s.include?('.') || REQUEST_HEADERS_TO_IGNORE.include?(key.to_s) }
|
46
|
+
begin
|
47
|
+
if request_headers["HTTP_AUTHORIZATION"].present?
|
48
|
+
if request_headers["HTTP_AUTHORIZATION"].include?("Basic")
|
49
|
+
user_password = request_headers["HTTP_AUTHORIZATION"].split("Basic").last.strip
|
50
|
+
user, password = Base64.decode64(user_password).split(":")
|
51
|
+
request_headers["HTTP_AUTHORIZATION"] = "Basic #{user}:ValueFiltered"
|
52
|
+
elsif
|
53
|
+
request_headers["HTTP_AUTHORIZATION"] = "ValueFiltered"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
request_headers["HTTP_API_TOKEN"] = "ValueFiltered" if request_headers["HTTP_API_TOKEN"].present?
|
57
|
+
rescue
|
58
|
+
request_headers.delete("HTTP_API_TOKEN")
|
59
|
+
request_headers.delete("HTTP_AUTHORIZATION")
|
60
|
+
end
|
61
|
+
items.merge!({ headers: request_headers.to_s })
|
62
|
+
end
|
63
|
+
|
64
|
+
if Thread.current[:appinstance].present?
|
65
|
+
items.merge!({connect_user: Thread.current[:appinstance].connect_user, new_session: Thread.current[:appinstance].new_session_message})
|
66
|
+
if Thread.current[:appinstance].logitems.present? && Thread.current[:appinstance].logitems.class == Hash
|
67
|
+
items.merge!(Thread.current[:appinstance].logitems)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
return items
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZuoraObservability
|
4
|
+
# Methods to get information about the application environment
|
5
|
+
class Env
|
6
|
+
class << self
|
7
|
+
def app_name
|
8
|
+
# parent_name is deprecated in Rails 6.0, removed in 6.1
|
9
|
+
ENV['DEIS_APP'].presence || Rails.application.class.parent_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def pod_name
|
13
|
+
ENV['HOSTNAME'].presence || Socket.gethostname
|
14
|
+
end
|
15
|
+
|
16
|
+
def full_process_name(process_name: nil, function: nil)
|
17
|
+
keys = [pod_name, process_name.presence || process_type, Process.pid, function]
|
18
|
+
keys.compact.join('][').prepend('[').concat(']')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the process type if any
|
22
|
+
def process_type(default: 'Unknown')
|
23
|
+
p_type = default
|
24
|
+
if ENV['HOSTNAME'] && ENV['DEIS_APP']
|
25
|
+
temp = ENV['HOSTNAME'].split(ENV['DEIS_APP'])[1]
|
26
|
+
temp = temp.split(/(-[0-9a-zA-Z]{5})$/)[0] # remove the 5 char hash
|
27
|
+
p_type = temp[1, temp.rindex("-")-1]
|
28
|
+
end
|
29
|
+
return p_type
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mono_logger'
|
4
|
+
|
5
|
+
module ZuoraObservability
|
6
|
+
# A configurable logger that can be used for Rails and additional libraries
|
7
|
+
module Logger
|
8
|
+
# NOTE(hartley): potentially change Logger module to delegate methods to
|
9
|
+
# value returned from custom_logger (so it ends up being a pure wrapper)
|
10
|
+
def self.custom_logger(name: "", level: Rails.logger.present? ? Rails.logger.level : MonoLogger::INFO, type: :ougai)
|
11
|
+
#puts name + ' - ' + {Logger::WARN => 'Logger::WARN', Logger::ERROR => 'Logger::ERROR', Logger::DEBUG => 'Logger::DEBUG', Logger::INFO => 'Logger::INFO' }[level] + ' - '
|
12
|
+
if type == :ougai
|
13
|
+
require 'ougai'
|
14
|
+
require "ougai/formatters/customizable"
|
15
|
+
#logger = Ougai::Logger.new(MonoLogger.new(STDOUT))
|
16
|
+
logger = Ougai::Logger.new(STDOUT)
|
17
|
+
logger.level = level
|
18
|
+
if ZuoraObservability.configuration.json_logging
|
19
|
+
require 'zuora_observability/logging/formatter'
|
20
|
+
logger.formatter = ZuoraObservability::Logging::Formatter.new(name)
|
21
|
+
logger.before_log = lambda do |data|
|
22
|
+
data[:trace_id] = ZuoraConnect::RequestIdMiddleware.request_id if ZuoraConnect::RequestIdMiddleware.request_id.present?
|
23
|
+
data[:zuora_trace_id] = ZuoraConnect::RequestIdMiddleware.zuora_request_id if ZuoraConnect::RequestIdMiddleware.zuora_request_id.present?
|
24
|
+
#data[:traces] = {amazon_id: data[:trace_id], zuora_id: data[:zuora_trace_id]}
|
25
|
+
end
|
26
|
+
else
|
27
|
+
logger.formatter = Ougai::Formatters::Customizable.new(
|
28
|
+
format_err: proc do |data|
|
29
|
+
next nil unless data.key?(:err)
|
30
|
+
err = data.delete(:err)
|
31
|
+
" #{err[:name]} (#{err[:message]})\n #{err[:stack]}"
|
32
|
+
end,
|
33
|
+
format_data: proc do |data|
|
34
|
+
data.delete(:app_instance_id); data.delete(:tenant_ids); data.delete(:organization); data.delete(:environment)
|
35
|
+
format('%s %s: %s', 'DATA'.ljust(6), Time.now.strftime('%FT%T.%6NZ'), "#{data.to_json}") if data.present?
|
36
|
+
end,
|
37
|
+
format_msg: proc do |severity, datetime, _progname, data|
|
38
|
+
msg = data.delete(:msg)
|
39
|
+
format('%s %s: %s', severity.ljust(6), datetime, msg)
|
40
|
+
end
|
41
|
+
)
|
42
|
+
logger.formatter.datetime_format = '%FT%T.%6NZ'
|
43
|
+
end
|
44
|
+
else
|
45
|
+
require 'mono_logger'
|
46
|
+
logger = MonoLogger.new(STDOUT)
|
47
|
+
logger.level = level
|
48
|
+
logger.formatter = proc do |serverity, datetime, progname, msg|
|
49
|
+
begin
|
50
|
+
msg = JSON.parse(msg)
|
51
|
+
rescue JSON::ParserError => ex
|
52
|
+
end
|
53
|
+
if ZuoraObservability.configuration.json_logging
|
54
|
+
require 'json'
|
55
|
+
store = {
|
56
|
+
name: name,
|
57
|
+
level: serverity,
|
58
|
+
timestamp: datetime.strftime('%FT%T.%6NZ'),
|
59
|
+
pid: Process.pid,
|
60
|
+
message: name == "ActionMailer" ? msg.strip : msg
|
61
|
+
}
|
62
|
+
JSON.dump(store) + "\n"
|
63
|
+
else
|
64
|
+
format('%s %s: %s', serverity.ljust(6), datetime, msg) + "\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
return logger
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ougai/formatters/base'
|
4
|
+
require 'ougai/formatters/for_json'
|
5
|
+
|
6
|
+
module ZuoraObservability
|
7
|
+
module Logging
|
8
|
+
# A JSON formatter compatible with node-bunyan
|
9
|
+
class Formatter < Ougai::Formatters::Base
|
10
|
+
include Ougai::Formatters::ForJson
|
11
|
+
|
12
|
+
# Intialize a formatter
|
13
|
+
# @param [String] app_name application name (execution program name if nil)
|
14
|
+
# @param [String] hostname hostname (hostname if nil)
|
15
|
+
# @param [Hash] opts the initial values of attributes
|
16
|
+
# @option opts [String] :trace_indent (2) the value of trace_indent attribute
|
17
|
+
# @option opts [String] :trace_max_lines (100) the value of trace_max_lines attribute
|
18
|
+
# @option opts [String] :serialize_backtrace (true) the value of serialize_backtrace attribute
|
19
|
+
# @option opts [String] :jsonize (true) the value of jsonize attribute
|
20
|
+
# @option opts [String] :with_newline (true) the value of with_newline attribute
|
21
|
+
def initialize(app_name = nil, hostname = nil, opts = {})
|
22
|
+
aname, hname, opts = Ougai::Formatters::Base.parse_new_params([app_name, hostname, opts])
|
23
|
+
super(aname, hname, opts)
|
24
|
+
init_opts_for_json(opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _call(severity, time, progname, data)
|
28
|
+
data.merge!({ message: data.delete(:msg) })
|
29
|
+
if data[:timestamp].present?
|
30
|
+
time = data[:timestamp]
|
31
|
+
data.delete(:timestamp)
|
32
|
+
end
|
33
|
+
dump({
|
34
|
+
name: progname || @app_name,
|
35
|
+
pid: $$,
|
36
|
+
level: severity,
|
37
|
+
timestamp: time.utc.strftime('%FT%T.%6NZ'),
|
38
|
+
}.merge(data))
|
39
|
+
end
|
40
|
+
|
41
|
+
def convert_time(data)
|
42
|
+
# data[:timestamp] = format_datetime(data[:time])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZuoraObservability
|
4
|
+
# Methods to gather and format metrics
|
5
|
+
module Metrics
|
6
|
+
@@telegraf_host = nil
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def write_to_telegraf(*args)
|
10
|
+
if ZuoraObservability.configuration.enable_metrics && !defined?(Prometheus)
|
11
|
+
@@telegraf_host = Metrics::Telegraf.new() if @@telegraf_host == nil
|
12
|
+
unicorn_stats = Metrics.unicorn_listener if defined?(Unicorn) && Unicorn.respond_to?(:listener_names)
|
13
|
+
@@telegraf_host.write(direction: 'Raindrops', tags: {}, values: unicorn_stats) unless unicorn_stats.blank?
|
14
|
+
return @@telegraf_host.write(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def resque
|
19
|
+
Resque.redis.ping
|
20
|
+
|
21
|
+
resque = Resque.info
|
22
|
+
|
23
|
+
{
|
24
|
+
app_name: ZuoraObservability::Env.app_name, url: 'dummy', Resque: {
|
25
|
+
Jobs_Finished: resque[:processed], Jobs_Failed: resque[:failed],
|
26
|
+
Jobs_Pending: resque[:pending], Workers_Active: resque[:working],
|
27
|
+
Workers_Total: resque[:workers]
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def versions
|
33
|
+
{
|
34
|
+
app_name: ZuoraObservability::Env.app_name,
|
35
|
+
url: 'dummy',
|
36
|
+
Version_Gem: ZuoraConnect::VERSION,
|
37
|
+
Version_Zuora: ZuoraAPI::VERSION,
|
38
|
+
Version_Ruby: RUBY_VERSION,
|
39
|
+
Version_Rails: Rails.version,
|
40
|
+
hold: 1
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def unicorn_listener
|
45
|
+
stats_hash = {}
|
46
|
+
stats_hash["total_active"] = 0
|
47
|
+
stats_hash["total_queued"] = 0
|
48
|
+
|
49
|
+
begin
|
50
|
+
tmp = Unicorn.listener_names
|
51
|
+
unix = tmp.grep(%r{\A/})
|
52
|
+
tcp = tmp.grep(/\A.+:\d+\z/)
|
53
|
+
tcp = nil if tcp.empty?
|
54
|
+
unix = nil if unix.empty?
|
55
|
+
|
56
|
+
|
57
|
+
Raindrops::Linux.tcp_listener_stats(tcp).each do |addr,stats|
|
58
|
+
stats_hash["active_#{addr}"] = stats.active
|
59
|
+
stats_hash["queued_#{addr}"] = stats.queued
|
60
|
+
stats_hash["total_active"] = stats.active + stats_hash["total_active"]
|
61
|
+
stats_hash["total_queued"] = stats.queued + stats_hash["total_queued"]
|
62
|
+
end if tcp
|
63
|
+
|
64
|
+
Raindrops::Linux.unix_listener_stats(unix).each do |addr,stats|
|
65
|
+
stats_hash["active_#{addr}"] = stats.active
|
66
|
+
stats_hash["queued_#{addr}"] = stats.queued
|
67
|
+
stats_hash["total_active"] = stats.active + stats_hash["total_active"]
|
68
|
+
stats_hash["total_queued"] = stats.queued + stats_hash["total_queued"]
|
69
|
+
end if unix
|
70
|
+
rescue IOError => ex
|
71
|
+
rescue => ex
|
72
|
+
Rails.logger.error(ex)
|
73
|
+
puts ex
|
74
|
+
end
|
75
|
+
return stats_hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# this looks copied from https://github.com/influxdata/influxdb-ruby, it may be
|
2
|
+
# worth just using the gem instead of vendoring the file
|
3
|
+
# module InfluxDB
|
4
|
+
module ZuoraObservability
|
5
|
+
module Metrics
|
6
|
+
# Convert data point to string using Line protocol
|
7
|
+
class PointValue
|
8
|
+
attr_reader :series, :values, :tags, :timestamp
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@series = escape data[:series], :measurement
|
12
|
+
@values = escape_values data[:values]
|
13
|
+
@tags = escape_tags data[:tags]
|
14
|
+
@timestamp = data[:timestamp]
|
15
|
+
end
|
16
|
+
|
17
|
+
def dump
|
18
|
+
dump = @series.dup
|
19
|
+
dump << ",#{@tags}" if @tags
|
20
|
+
dump << " #{@values}"
|
21
|
+
dump << " #{@timestamp}" if @timestamp
|
22
|
+
dump
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
ESCAPES = {
|
28
|
+
measurement: [' '.freeze, ','.freeze],
|
29
|
+
tag_key: ['='.freeze, ' '.freeze, ','.freeze],
|
30
|
+
tag_value: ['='.freeze, ' '.freeze, ','.freeze],
|
31
|
+
field_key: ['='.freeze, ' '.freeze, ','.freeze, '"'.freeze],
|
32
|
+
field_value: ["\\".freeze, '"'.freeze],
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
private_constant :ESCAPES
|
36
|
+
|
37
|
+
def escape(str, type)
|
38
|
+
# rubocop:disable Layout/AlignParameters
|
39
|
+
str = str.encode "UTF-8".freeze, "UTF-8".freeze,
|
40
|
+
invalid: :replace,
|
41
|
+
undef: :replace,
|
42
|
+
replace: "".freeze
|
43
|
+
# rubocop:enable Layout/AlignParameters
|
44
|
+
|
45
|
+
ESCAPES[type].each do |ch|
|
46
|
+
str = str.gsub(ch) { "\\#{ch}" }
|
47
|
+
end
|
48
|
+
str
|
49
|
+
end
|
50
|
+
|
51
|
+
def escape_values(values)
|
52
|
+
return if values.nil?
|
53
|
+
values.map do |k, v|
|
54
|
+
key = escape(k.to_s, :field_key)
|
55
|
+
val = escape_value(v)
|
56
|
+
"#{key}=#{val}"
|
57
|
+
end.join(",".freeze)
|
58
|
+
end
|
59
|
+
|
60
|
+
def escape_value(value)
|
61
|
+
if value.is_a?(String)
|
62
|
+
'"'.freeze + escape(value, :field_value) + '"'.freeze
|
63
|
+
elsif value.is_a?(Integer)
|
64
|
+
"#{value}i"
|
65
|
+
else
|
66
|
+
value.to_s
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def escape_tags(tags)
|
71
|
+
return if tags.nil?
|
72
|
+
|
73
|
+
tags = tags.map do |k, v|
|
74
|
+
key = escape(k.to_s, :tag_key)
|
75
|
+
val = escape(v.to_s, :tag_value)
|
76
|
+
|
77
|
+
"#{key}=#{val}" unless key == "".freeze || val == "".freeze
|
78
|
+
end.compact
|
79
|
+
|
80
|
+
tags.join(",") unless tags.empty?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZuoraObservability
|
4
|
+
module Metrics
|
5
|
+
# Functionality for sending metrics to a Telegraf endpoint
|
6
|
+
#
|
7
|
+
# it looks like https://github.com/influxdata/influxdb-ruby may provide some
|
8
|
+
# more high level abstractions instead of using a UDPSocket directly
|
9
|
+
class Telegraf
|
10
|
+
attr_accessor :host
|
11
|
+
|
12
|
+
OUTBOUND_METRICS = true
|
13
|
+
OUTBOUND_METRICS_NAME = 'request-outbound'
|
14
|
+
INBOUND_METRICS = true
|
15
|
+
INBOUND_METRICS_NAME = 'request-inbound'
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
connect
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect
|
22
|
+
# TODO(hartley): this Rails logger was originally ZuoraConnect.logger
|
23
|
+
Rails.logger.debug(format_metric_log('Telegraf', 'Need new connection')) if ZuoraObservability.configuration.telegraf_debug
|
24
|
+
uri = URI.parse(ZuoraObservability.configuration.telegraf_endpoint)
|
25
|
+
self.host = UDPSocket.new.tap do |socket|
|
26
|
+
socket.connect uri.host, uri.port
|
27
|
+
end
|
28
|
+
rescue => ex
|
29
|
+
self.host = nil
|
30
|
+
# TODO(hartley): this Rails logger was originally ZuoraConnect.logger
|
31
|
+
Rails.logger.warn(self.format_metric_log('Telegraf', "Failed to connect: #{ex.class}")) if Rails.env.to_s != 'production'
|
32
|
+
end
|
33
|
+
|
34
|
+
def write(direction: 'Unknown', tags: {}, values: {})
|
35
|
+
time = Benchmark.measure do
|
36
|
+
# To avoid writing metrics from rspec tests
|
37
|
+
if Rails.env.to_sym != :test
|
38
|
+
app_instance = Thread.current[:appinstance].present? ? Thread.current[:appinstance].id : 0
|
39
|
+
tags = {
|
40
|
+
app_name: Env.app_name, process_type: Env.process_type,
|
41
|
+
app_instance: app_instance, pod_name: Env.pod_name
|
42
|
+
}.merge(tags)
|
43
|
+
|
44
|
+
if direction == :inbound
|
45
|
+
# This condition relies on a monkey patch in the connect gem that
|
46
|
+
# adds a to_bool method for Nil, True, and False that are not
|
47
|
+
# present by default
|
48
|
+
if INBOUND_METRICS && !Thread.current[:inbound_metric].to_bool
|
49
|
+
self.write_udp(series: INBOUND_METRICS_NAME, tags: tags, values: values)
|
50
|
+
Thread.current[:inbound_metric] = true
|
51
|
+
else
|
52
|
+
return
|
53
|
+
end
|
54
|
+
elsif direction == :outbound
|
55
|
+
write_udp(series: OUTBOUND_METRICS_NAME, tags: tags, values: values) if OUTBOUND_METRICS
|
56
|
+
else
|
57
|
+
write_udp(series: direction, tags: tags, values: values)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
return unless ZuoraObservability.configuration.telegraf_debug
|
63
|
+
|
64
|
+
# TODO(hartley): these Rails loggers were originally ZuoraConnect.logger
|
65
|
+
Rails.logger.debug(format_metric_log('Telegraf', tags.to_s))
|
66
|
+
Rails.logger.debug(format_metric_log('Telegraf', values.to_s))
|
67
|
+
Rails.logger.debug(
|
68
|
+
format_metric_log(
|
69
|
+
'Telegraf',
|
70
|
+
"Writing '#{direction.capitalize}': #{time.real.round(5)} ms"
|
71
|
+
)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def write_udp(series: '', tags: {}, values: {})
|
76
|
+
return if values.blank?
|
77
|
+
|
78
|
+
host.write PointValue.new({ series: series, tags: tags, values: values }).dump
|
79
|
+
rescue => ex
|
80
|
+
self.connect
|
81
|
+
ZuoraConnect.logger.warn(self.format_metric_log('Telegraf', "Failed to write udp: #{ex.class}")) if Rails.env.to_s != 'production'
|
82
|
+
end
|
83
|
+
|
84
|
+
def format_metric_log(message, dump = nil)
|
85
|
+
message_color = '1;91'
|
86
|
+
dump_color = '0;1'
|
87
|
+
log_entry = " \e[#{message_color}m#{message}\e[0m #{
|
88
|
+
"\e[#{dump_color}m#{dump}\e[0m" if dump
|
89
|
+
}"
|
90
|
+
if Rails.env.development?
|
91
|
+
log_entry
|
92
|
+
else
|
93
|
+
[message, dump].compact.join(' - ')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zuora_observability
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.a
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hartley McGuire
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lograge
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ougai
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ougai-formatters-customizable
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mono_logger
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: brakeman
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: resque
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec_junit_formatter
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec-rails
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.2'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop-rails
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop-rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 2.0.0.pre
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 2.0.0.pre
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: simplecov
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: simplecov-cobertura
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: sqlite3
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: unicorn
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
description: Description of ZuoraObservability.
|
238
|
+
email:
|
239
|
+
- hmcguire@zuora.com
|
240
|
+
executables: []
|
241
|
+
extensions: []
|
242
|
+
extra_rdoc_files: []
|
243
|
+
files:
|
244
|
+
- MIT-LICENSE
|
245
|
+
- README.md
|
246
|
+
- Rakefile
|
247
|
+
- app/assets/config/zuora_observability_manifest.js
|
248
|
+
- app/assets/stylesheets/zuora_observability/application.css
|
249
|
+
- app/controllers/zuora_observability/application_controller.rb
|
250
|
+
- app/controllers/zuora_observability/metrics_controller.rb
|
251
|
+
- app/helpers/zuora_observability/application_helper.rb
|
252
|
+
- app/jobs/zuora_observability/application_job.rb
|
253
|
+
- app/mailers/zuora_observability/application_mailer.rb
|
254
|
+
- app/models/zuora_observability/application_record.rb
|
255
|
+
- app/views/layouts/zuora_observability/application.html.erb
|
256
|
+
- config/routes.rb
|
257
|
+
- lib/tasks/zuora_observability_tasks.rake
|
258
|
+
- lib/zuora_observability.rb
|
259
|
+
- lib/zuora_observability/configuration.rb
|
260
|
+
- lib/zuora_observability/engine.rb
|
261
|
+
- lib/zuora_observability/env.rb
|
262
|
+
- lib/zuora_observability/logger.rb
|
263
|
+
- lib/zuora_observability/logging/formatter.rb
|
264
|
+
- lib/zuora_observability/metrics.rb
|
265
|
+
- lib/zuora_observability/metrics/point_value.rb
|
266
|
+
- lib/zuora_observability/metrics/telegraf.rb
|
267
|
+
- lib/zuora_observability/version.rb
|
268
|
+
homepage:
|
269
|
+
licenses:
|
270
|
+
- MIT
|
271
|
+
metadata: {}
|
272
|
+
post_install_message:
|
273
|
+
rdoc_options: []
|
274
|
+
require_paths:
|
275
|
+
- lib
|
276
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
277
|
+
requirements:
|
278
|
+
- - ">="
|
279
|
+
- !ruby/object:Gem::Version
|
280
|
+
version: '2.4'
|
281
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
282
|
+
requirements:
|
283
|
+
- - ">"
|
284
|
+
- !ruby/object:Gem::Version
|
285
|
+
version: 1.3.1
|
286
|
+
requirements: []
|
287
|
+
rubygems_version: 3.1.4
|
288
|
+
signing_key:
|
289
|
+
specification_version: 4
|
290
|
+
summary: Summary of ZuoraObservability.
|
291
|
+
test_files: []
|