zuora_observability 0.1.0.pre.a → 0.3.5
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 +4 -4
- data/README.md +12 -2
- data/config/initializers/loggers.rb +24 -0
- data/config/initializers/tagged_logging.rb +39 -0
- data/lib/zuora_observability/configuration.rb +11 -6
- data/lib/zuora_observability/engine.rb +24 -53
- data/lib/zuora_observability/env.rb +23 -2
- data/lib/zuora_observability/logger.rb +60 -37
- data/lib/zuora_observability/logging/custom_options.rb +79 -0
- data/lib/zuora_observability/logging/formatter.rb +179 -33
- data/lib/zuora_observability/metrics.rb +0 -11
- data/lib/zuora_observability/version.rb +2 -2
- data/lib/zuora_observability.rb +0 -2
- metadata +35 -39
- data/app/assets/config/zuora_observability_manifest.js +0 -1
- data/app/assets/stylesheets/zuora_observability/application.css +0 -15
- data/app/jobs/zuora_observability/application_job.rb +0 -4
- data/app/mailers/zuora_observability/application_mailer.rb +0 -6
- data/app/models/zuora_observability/application_record.rb +0 -5
- data/lib/zuora_observability/metrics/point_value.rb +0 -84
- data/lib/zuora_observability/metrics/telegraf.rb +0 -98
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2f179eca39cb93afe9e161797593b9b36e0b99cc4b2f002c98e549a0eac24b17
|
|
4
|
+
data.tar.gz: 041fdacdc8805507528d04bbc0e73c3de85f16d60e3d92b15c0a9715650c1015
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0ef15e3d143be63cadc70a1249b18e4d10be281d1a7fe30523beae340d85e8dc2bc141f30a73e3dda0162e4bc3171848f87d11509ab26bfa9c6bae3d9737f0a
|
|
7
|
+
data.tar.gz: 35ae00955747c5da4e62e58f9634157618d07d5b8749230b86158f0ae9691b0e6306e49f96b34a9702e4f2c0d87ebb36d37e97b705127fb9a6e429f1d1a7d646
|
data/README.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A ruby gem to enable observability into rails applications
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Ruby 2.5+
|
|
8
|
+
- Rails 5.0+
|
|
7
9
|
|
|
8
10
|
## Installation
|
|
11
|
+
|
|
9
12
|
Add this line to your application's Gemfile:
|
|
10
13
|
|
|
11
14
|
```ruby
|
|
@@ -13,14 +16,21 @@ gem 'zuora_observability'
|
|
|
13
16
|
```
|
|
14
17
|
|
|
15
18
|
And then execute:
|
|
19
|
+
|
|
16
20
|
```bash
|
|
17
21
|
$ bundle
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
Or install it yourself as:
|
|
25
|
+
|
|
21
26
|
```bash
|
|
22
27
|
$ gem install zuora_observability
|
|
23
28
|
```
|
|
24
29
|
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
- [Logging](doc/logging.md)
|
|
33
|
+
|
|
25
34
|
## License
|
|
35
|
+
|
|
26
36
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
if defined?(Resque.logger)
|
|
4
|
+
Resque.logger = ZuoraObservability::Logger.custom_logger(name: 'Resque')
|
|
5
|
+
if defined?(Resque::Scheduler)
|
|
6
|
+
Resque::Scheduler.logger = ZuoraObservability::Logger.custom_logger(name: 'ResqueScheduler')
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
if defined?(Delayed::Worker.logger)
|
|
11
|
+
Delayed::Worker.logger = ZuoraObservability::Logger.custom_logger(name: 'DelayedJob', level: Logger::INFO)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if defined?(Makara)
|
|
15
|
+
Makara::Logging::Logger.logger = ZuoraObservability::Logger.custom_logger(name: 'Makara')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if defined?(ElasticAPM) && ElasticAPM.running?
|
|
19
|
+
ElasticAPM.agent.config.logger = ZuoraObservability::Logger.custom_logger(name: 'ElasticAPM', level: Logger::WARN)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if defined?(ActionMailer)
|
|
23
|
+
ActionMailer::Base.logger = ZuoraObservability::Logger.custom_logger(name: 'ActionMailer')
|
|
24
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module TaggedLogging
|
|
5
|
+
# Patch to apply structured tags to Tagged Logs
|
|
6
|
+
module Formatter
|
|
7
|
+
def call(severity, timestamp, progname, data)
|
|
8
|
+
super(severity, timestamp, progname, merged_tags.merge!(data))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def merged_tags
|
|
14
|
+
tags = {}
|
|
15
|
+
|
|
16
|
+
current_tags.each do |tag|
|
|
17
|
+
tags.merge!(tag) if tag.is_a?(Hash)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
tags
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module Rails
|
|
27
|
+
module Rack
|
|
28
|
+
# Patch to compute structured tags for Tagged Logging
|
|
29
|
+
class Logger
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def compute_tags(request)
|
|
33
|
+
{
|
|
34
|
+
trace_id: request.uuid
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
module ZuoraObservability
|
|
4
4
|
# Global configuration that can be set in a Rails initializer
|
|
5
5
|
class Configuration
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
attr_reader :custom_payload_hooks
|
|
7
|
+
attr_accessor :zecs_service_hook, :json_logging
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
10
|
-
@
|
|
11
|
-
@
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
@custom_payload_hooks = []
|
|
11
|
+
@zecs_service_hook = nil
|
|
12
|
+
# NOTE(hartley): this checks the var for presence rather than value to
|
|
13
|
+
# align with how Rails does RAILS_LOG_TO_STDOUT
|
|
14
|
+
@json_logging = ENV['ZUORA_JSON_LOGGING'].present? ? true : !(Rails.env.development? || Rails.env.test?)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_custom_payload_hook(&block)
|
|
18
|
+
custom_payload_hooks << block
|
|
14
19
|
end
|
|
15
20
|
end
|
|
16
21
|
end
|
|
@@ -10,64 +10,35 @@ module ZuoraObservability
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
initializer(:rails_stdout_logging, before: :initialize_logger) do
|
|
13
|
-
|
|
13
|
+
Rails.application.configure do
|
|
14
|
+
config.logger = ZuoraObservability::Logger.custom_logger(name: 'Rails')
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
if !Rails.env.test? && !Rails.env.development?
|
|
17
|
-
Rails.configuration.lograge.enabled = true
|
|
18
|
-
Rails.configuration.colorize_logging = false
|
|
19
|
-
end
|
|
16
|
+
next unless ZuoraObservability.configuration.json_logging
|
|
20
17
|
|
|
21
|
-
|
|
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?
|
|
18
|
+
config.logger = ActiveSupport::TaggedLogging.new(config.logger)
|
|
38
19
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
20
|
+
require 'lograge'
|
|
21
|
+
require 'zuora_observability/logging/custom_options'
|
|
22
|
+
|
|
23
|
+
config.lograge.enabled = true
|
|
24
|
+
config.colorize_logging = false
|
|
63
25
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
26
|
+
config.lograge.formatter = Lograge::Formatters::Raw.new
|
|
27
|
+
|
|
28
|
+
config.lograge.custom_options = Logging::CustomOptions
|
|
29
|
+
config.lograge.custom_payload do |controller|
|
|
30
|
+
payload = {}
|
|
31
|
+
|
|
32
|
+
ZuoraObservability.configuration.custom_payload_hooks.each do |hook|
|
|
33
|
+
payload.merge!(hook.call(controller))
|
|
69
34
|
end
|
|
70
|
-
|
|
35
|
+
|
|
36
|
+
next payload unless ZuoraObservability.configuration.zecs_service_hook
|
|
37
|
+
|
|
38
|
+
payload[:zecs_service] =
|
|
39
|
+
ZuoraObservability.configuration.zecs_service_hook.call(controller)
|
|
40
|
+
|
|
41
|
+
payload
|
|
71
42
|
end
|
|
72
43
|
end
|
|
73
44
|
end
|
|
@@ -4,9 +4,20 @@ module ZuoraObservability
|
|
|
4
4
|
# Methods to get information about the application environment
|
|
5
5
|
class Env
|
|
6
6
|
class << self
|
|
7
|
+
def name
|
|
8
|
+
ENV['Z_APPLICATION_NAME']
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def version
|
|
12
|
+
ENV['Z_APPLICATION_VERSION']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def environment
|
|
16
|
+
ENV['Z_APPLICATION_ENVIRONMENT']
|
|
17
|
+
end
|
|
18
|
+
|
|
7
19
|
def app_name
|
|
8
|
-
|
|
9
|
-
ENV['DEIS_APP'].presence || Rails.application.class.parent_name
|
|
20
|
+
ENV['DEIS_APP'].presence || app_parent_name
|
|
10
21
|
end
|
|
11
22
|
|
|
12
23
|
def pod_name
|
|
@@ -28,6 +39,16 @@ module ZuoraObservability
|
|
|
28
39
|
end
|
|
29
40
|
return p_type
|
|
30
41
|
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def app_parent_name
|
|
46
|
+
if Rails::VERSION::MAJOR >= 6
|
|
47
|
+
Rails.application.class.module_parent_name
|
|
48
|
+
else
|
|
49
|
+
Rails.application.class.parent_name
|
|
50
|
+
end
|
|
51
|
+
end
|
|
31
52
|
end
|
|
32
53
|
end
|
|
33
54
|
end
|
|
@@ -1,47 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'ougai'
|
|
3
4
|
require 'mono_logger'
|
|
5
|
+
require 'zuora_observability/logging/formatter'
|
|
6
|
+
require 'ougai/formatters/customizable'
|
|
4
7
|
|
|
5
8
|
module ZuoraObservability
|
|
6
9
|
# A configurable logger that can be used for Rails and additional libraries
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
def
|
|
11
|
-
|
|
10
|
+
class Logger < Ougai::Logger
|
|
11
|
+
# TODO(hartley): enable silence method for Rails 5.2 asset requests
|
|
12
|
+
# https://github.com/tilfin/ougai/wiki/Use-as-Rails-logger
|
|
13
|
+
def initialize(logdev, **)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
# NOTE(hartley): the purpose for the original split between Ougai and
|
|
17
|
+
# MonoLogger was that MonoLogger enables logging in a trap context
|
|
18
|
+
# https://github.com/tilfin/ougai/issues/74
|
|
19
|
+
# By using our own Logger class, we can override the LogDevice created
|
|
20
|
+
# by ruby with MonoLogger's, enabling logging in a trap context
|
|
21
|
+
@logdev = MonoLogger::LocklessLogDevice.new(logdev)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_formatter
|
|
25
|
+
return ZuoraObservability::Logging::Formatter.new if ZuoraObservability.configuration.json_logging
|
|
26
|
+
|
|
27
|
+
formatter = Ougai::Formatters::Customizable.new(
|
|
28
|
+
format_err: method(:custom_error_formatter),
|
|
29
|
+
format_data: method(:custom_data_formatter),
|
|
30
|
+
format_msg: method(:custom_message_formatter)
|
|
31
|
+
)
|
|
32
|
+
formatter.datetime_format = '%FT%T.%3NZ'
|
|
33
|
+
formatter
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def custom_message_formatter(severity, datetime, _progname, data)
|
|
37
|
+
msg = data.delete(:msg)
|
|
38
|
+
"#{severity.ljust(6)} #{datetime}: #{msg}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def custom_data_formatter(data)
|
|
42
|
+
data.delete_if do |k|
|
|
43
|
+
%i[app_instance_id tenant_ids organization environment].include? k
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return nil if data.blank?
|
|
47
|
+
|
|
48
|
+
"#{'DATA'.ljust(6)} #{Time.current.strftime('%FT%T.%3NZ')}: #{data.to_json}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def custom_error_formatter(data)
|
|
52
|
+
return nil unless data.key?(:err)
|
|
53
|
+
|
|
54
|
+
err = data.delete(:err)
|
|
55
|
+
" #{err[:name]} (#{err[:message]})\n #{err[:stack]}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.custom_logger(name: '', level: Rails.logger.present? ? Rails.logger.level : MonoLogger::INFO, type: :ougai)
|
|
12
59
|
if type == :ougai
|
|
13
|
-
|
|
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
|
|
60
|
+
logger = new($stdout, level: level, progname: name)
|
|
44
61
|
else
|
|
62
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
63
|
+
Creating a non-ougai custom_logger is deprecated and will be removed
|
|
64
|
+
in a future version of zuora_observability.
|
|
65
|
+
MSG
|
|
66
|
+
|
|
45
67
|
require 'mono_logger'
|
|
46
68
|
logger = MonoLogger.new(STDOUT)
|
|
47
69
|
logger.level = level
|
|
@@ -65,7 +87,8 @@ module ZuoraObservability
|
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
end
|
|
68
|
-
|
|
90
|
+
|
|
91
|
+
logger
|
|
69
92
|
end
|
|
70
93
|
end
|
|
71
94
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ZuoraObservability
|
|
4
|
+
module Logging
|
|
5
|
+
# Custom Options for lograge to add additional fields to logged data
|
|
6
|
+
module CustomOptions
|
|
7
|
+
IGNORE_HEADERS = %w[
|
|
8
|
+
RAW_POST_DATA
|
|
9
|
+
REQUEST_METHOD
|
|
10
|
+
REQUEST_URI
|
|
11
|
+
REQUEST_PATH
|
|
12
|
+
PATH_INFO
|
|
13
|
+
CONTENT_TYPE
|
|
14
|
+
ORIGINAL_FULLPATH
|
|
15
|
+
QUERY_STRING
|
|
16
|
+
HTTP_COOKIE
|
|
17
|
+
HTTP_X_ENVOY_PEER_METADATA
|
|
18
|
+
HTTP_X_ENVOY_DECORATOR_OPERATION
|
|
19
|
+
HTTP_ZSESSION
|
|
20
|
+
HTTP_SEC_CH_UA_MOBILE
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
IGNORE_PARAMS = %w[controller action format].freeze
|
|
24
|
+
|
|
25
|
+
FILTERED = '[FILTERED]'
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def call(event)
|
|
29
|
+
items = {
|
|
30
|
+
msg: 'Rails Request',
|
|
31
|
+
params: event.payload[:params].as_json(except: IGNORE_PARAMS).to_s,
|
|
32
|
+
zuora_trace_id: event.payload[:headers]['HTTP_ZUORA_REQUEST_ID'],
|
|
33
|
+
error: event.payload[:exception_object]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if event.payload[:headers].present?
|
|
37
|
+
request_headers = filter_request_headers(event.payload[:headers])
|
|
38
|
+
|
|
39
|
+
filtered_headers = filter_sensitive_headers(request_headers)
|
|
40
|
+
|
|
41
|
+
items.merge!({ headers: filtered_headers })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
items
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def filter_request_headers(action_dispatch_headers)
|
|
50
|
+
action_dispatch_headers.env.select do |header|
|
|
51
|
+
next false if IGNORE_HEADERS.include? header
|
|
52
|
+
|
|
53
|
+
header =~ /^HTTP_/ || ActionDispatch::Http::Headers::CGI_VARIABLES.include?(header)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def filter_sensitive_headers(headers)
|
|
58
|
+
filtered = {}
|
|
59
|
+
|
|
60
|
+
auth = headers['HTTP_AUTHORIZATION']
|
|
61
|
+
filtered['HTTP_AUTHORIZATION'] = filter_auth_header(auth) if auth
|
|
62
|
+
|
|
63
|
+
filtered['HTTP_API_TOKEN'] = FILTERED if headers['HTTP_API_TOKEN']
|
|
64
|
+
|
|
65
|
+
headers.merge(filtered)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def filter_auth_header(authorization)
|
|
69
|
+
return FILTERED unless authorization.include? 'Basic'
|
|
70
|
+
|
|
71
|
+
encoded = authorization.split(' ', 2).second
|
|
72
|
+
user, _password = Base64.decode64(encoded).split(':')
|
|
73
|
+
|
|
74
|
+
"Basic #{user}:#{FILTERED}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,45 +1,191 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ougai/formatters/base'
|
|
4
|
-
require 'ougai/formatters/for_json'
|
|
5
|
-
|
|
6
3
|
module ZuoraObservability
|
|
7
4
|
module Logging
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
5
|
+
# Formats data into ZECS Logging Standard and dumps as JSON
|
|
6
|
+
# https://tag.pages.gitlab.zeta.tools/standards/logging
|
|
7
|
+
class Formatter < Ougai::Formatters::Bunyan
|
|
8
|
+
ECS_VERSION = '1.5.0'
|
|
9
|
+
|
|
10
|
+
ZECS_TOP_LEVEL_FIELDS = %i[
|
|
11
|
+
error event http organization process url user
|
|
12
|
+
].freeze
|
|
13
|
+
ECS_HTTP_FIELDS = %i[request response].freeze
|
|
26
14
|
|
|
27
15
|
def _call(severity, time, progname, data)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
timestamp: time.utc.strftime('%FT%T.%6NZ'),
|
|
38
|
-
}.merge(data))
|
|
16
|
+
output = base_fields(severity, time, progname, data)
|
|
17
|
+
|
|
18
|
+
other_fields = get_fields_for(ZECS_TOP_LEVEL_FIELDS, data)
|
|
19
|
+
output.merge!(other_fields)
|
|
20
|
+
|
|
21
|
+
zuora_fields = ZuoraFields.call(data)
|
|
22
|
+
output[:zuora] = zuora_fields unless zuora_fields.empty?
|
|
23
|
+
|
|
24
|
+
dump(output)
|
|
39
25
|
end
|
|
40
26
|
|
|
27
|
+
# Note(hartley): Ougai::Formatters::ForJson requires this be present
|
|
41
28
|
def convert_time(data)
|
|
42
|
-
|
|
29
|
+
data[:@timestamp] = data[:@timestamp].utc.iso8601(3)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def get_fields_for(list, data)
|
|
35
|
+
final_hash = {}
|
|
36
|
+
|
|
37
|
+
list.each do |field|
|
|
38
|
+
field_output = send("#{field}_fields", data)
|
|
39
|
+
final_hash[field] = field_output unless field_output.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
final_hash
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Fields required by ECS
|
|
46
|
+
def base_fields(severity, time, progname, data)
|
|
47
|
+
{
|
|
48
|
+
:@timestamp => time,
|
|
49
|
+
message: data[:msg],
|
|
50
|
+
ecs: { version: ECS_VERSION },
|
|
51
|
+
log: { level: severity, logger: progname || @app_name },
|
|
52
|
+
service: { name: Env.name, version: Env.version },
|
|
53
|
+
trace: { id: data[:trace_id] },
|
|
54
|
+
tags: ['zecs']
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# errors
|
|
59
|
+
def error_fields(data)
|
|
60
|
+
return {} unless data[:error] || data[:err]
|
|
61
|
+
|
|
62
|
+
error = data[:err] || serialize_exc(data[:error])
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
message: error[:message],
|
|
66
|
+
stack_trace: error[:stack],
|
|
67
|
+
type: error[:name]
|
|
68
|
+
}.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# http
|
|
72
|
+
def http_fields(data)
|
|
73
|
+
get_fields_for(ECS_HTTP_FIELDS, data)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# http.request
|
|
77
|
+
def request_fields(data)
|
|
78
|
+
request = data.slice(:method, :params).merge(data.fetch(:request, {}))
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
method: request[:method],
|
|
82
|
+
body: ({ content: request[:params] } if request.key? :params)
|
|
83
|
+
}.compact
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# http.response
|
|
87
|
+
def response_fields(data)
|
|
88
|
+
response = data.slice(:status).merge(data.fetch(:response, {}))
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
status_code: response[:status],
|
|
92
|
+
body: ({ content: response[:params] } if response.key? :params),
|
|
93
|
+
}.compact
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# organization
|
|
97
|
+
def organization_fields(data)
|
|
98
|
+
{
|
|
99
|
+
name: data[:organization]
|
|
100
|
+
}.compact
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# process
|
|
104
|
+
def process_fields(_data)
|
|
105
|
+
{ id: Process.pid }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# url
|
|
109
|
+
def url_fields(data)
|
|
110
|
+
{
|
|
111
|
+
full: data.dig(:url, :full),
|
|
112
|
+
path: data[:path]
|
|
113
|
+
}.compact
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# user
|
|
117
|
+
def user_fields(data)
|
|
118
|
+
{
|
|
119
|
+
id: data[:user_id],
|
|
120
|
+
email: data[:email]
|
|
121
|
+
}.compact
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# event
|
|
125
|
+
def event_fields(data)
|
|
126
|
+
full_action = "#{data[:controller]}##{data[:action]}"
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
action: (full_action unless full_action == '#'),
|
|
130
|
+
duration: data[:duration]
|
|
131
|
+
}.compact
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# The Zuora Extension to ECS defined by ZECS
|
|
136
|
+
module ZuoraFields
|
|
137
|
+
ZECS_VERSION = '1.1'
|
|
138
|
+
|
|
139
|
+
class << self
|
|
140
|
+
# zuora top level field
|
|
141
|
+
def call(data)
|
|
142
|
+
hash = base_fields(data)
|
|
143
|
+
|
|
144
|
+
z_http = http_fields(data)
|
|
145
|
+
hash[:http] = z_http unless z_http.empty?
|
|
146
|
+
|
|
147
|
+
z_service = service_fields(data)
|
|
148
|
+
hash[Env.name.to_sym] = z_service unless z_service.empty?
|
|
149
|
+
|
|
150
|
+
return {} if hash.empty?
|
|
151
|
+
|
|
152
|
+
hash.merge({ ecs: { version: ZECS_VERSION } })
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def base_fields(data)
|
|
156
|
+
{
|
|
157
|
+
cp_id: data[:zuora_track_id],
|
|
158
|
+
entity_ids: data[:entity_ids],
|
|
159
|
+
environment: data[:environment],
|
|
160
|
+
tenant_id: data[:tenant_ids],
|
|
161
|
+
trace_id: data[:zuora_trace_id]
|
|
162
|
+
}.compact
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# zuora.http
|
|
166
|
+
def http_fields(data)
|
|
167
|
+
z_http_request = {
|
|
168
|
+
headers: data[:headers] || data.dig(:request, :headers),
|
|
169
|
+
headers_blob: data.dig(:request, :headers_blob)
|
|
170
|
+
}.compact
|
|
171
|
+
|
|
172
|
+
z_http_response = {
|
|
173
|
+
headers: data.dig(:response, :headers),
|
|
174
|
+
headers_blob: data.dig(:response, :headers_blob)
|
|
175
|
+
}.compact
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
request: (z_http_request unless z_http_request.empty?),
|
|
179
|
+
response: (z_http_response unless z_http_response.empty?)
|
|
180
|
+
}.compact
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Service's Custom Fields
|
|
184
|
+
def service_fields(data)
|
|
185
|
+
{
|
|
186
|
+
app_instance_id: data[:app_instance_id]
|
|
187
|
+
}.merge(data.fetch(:zecs_service, {})).compact
|
|
188
|
+
end
|
|
43
189
|
end
|
|
44
190
|
end
|
|
45
191
|
end
|
|
@@ -3,18 +3,7 @@
|
|
|
3
3
|
module ZuoraObservability
|
|
4
4
|
# Methods to gather and format metrics
|
|
5
5
|
module Metrics
|
|
6
|
-
@@telegraf_host = nil
|
|
7
|
-
|
|
8
6
|
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
7
|
def resque
|
|
19
8
|
Resque.redis.ping
|
|
20
9
|
|
data/lib/zuora_observability.rb
CHANGED
|
@@ -7,8 +7,6 @@ require 'zuora_observability/metrics'
|
|
|
7
7
|
require 'zuora_observability/logger'
|
|
8
8
|
|
|
9
9
|
require 'zuora_observability/logging/formatter'
|
|
10
|
-
require 'zuora_observability/metrics/telegraf'
|
|
11
|
-
require 'zuora_observability/metrics/point_value'
|
|
12
10
|
|
|
13
11
|
# Provides Rails application with tools for observabilty
|
|
14
12
|
module ZuoraObservability
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zuora_observability
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hartley McGuire
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-10-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lograge
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: mono_logger
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: ougai
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,20 +66,6 @@ dependencies:
|
|
|
52
66
|
- - '='
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
68
|
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
69
|
- !ruby/object:Gem::Dependency
|
|
70
70
|
name: rails
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -140,16 +140,16 @@ dependencies:
|
|
|
140
140
|
name: rubocop
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
142
142
|
requirements:
|
|
143
|
-
- - "
|
|
143
|
+
- - ">="
|
|
144
144
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '
|
|
145
|
+
version: '0'
|
|
146
146
|
type: :development
|
|
147
147
|
prerelease: false
|
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
149
|
requirements:
|
|
150
|
-
- - "
|
|
150
|
+
- - ">="
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '
|
|
152
|
+
version: '0'
|
|
153
153
|
- !ruby/object:Gem::Dependency
|
|
154
154
|
name: rubocop-rails
|
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -168,16 +168,16 @@ dependencies:
|
|
|
168
168
|
name: rubocop-rspec
|
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
|
170
170
|
requirements:
|
|
171
|
-
- - "
|
|
171
|
+
- - ">="
|
|
172
172
|
- !ruby/object:Gem::Version
|
|
173
|
-
version:
|
|
173
|
+
version: '0'
|
|
174
174
|
type: :development
|
|
175
175
|
prerelease: false
|
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
|
177
177
|
requirements:
|
|
178
|
-
- - "
|
|
178
|
+
- - ">="
|
|
179
179
|
- !ruby/object:Gem::Version
|
|
180
|
-
version:
|
|
180
|
+
version: '0'
|
|
181
181
|
- !ruby/object:Gem::Dependency
|
|
182
182
|
name: simplecov
|
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -244,15 +244,12 @@ files:
|
|
|
244
244
|
- MIT-LICENSE
|
|
245
245
|
- README.md
|
|
246
246
|
- Rakefile
|
|
247
|
-
- app/assets/config/zuora_observability_manifest.js
|
|
248
|
-
- app/assets/stylesheets/zuora_observability/application.css
|
|
249
247
|
- app/controllers/zuora_observability/application_controller.rb
|
|
250
248
|
- app/controllers/zuora_observability/metrics_controller.rb
|
|
251
249
|
- 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
250
|
- app/views/layouts/zuora_observability/application.html.erb
|
|
251
|
+
- config/initializers/loggers.rb
|
|
252
|
+
- config/initializers/tagged_logging.rb
|
|
256
253
|
- config/routes.rb
|
|
257
254
|
- lib/tasks/zuora_observability_tasks.rake
|
|
258
255
|
- lib/zuora_observability.rb
|
|
@@ -260,16 +257,15 @@ files:
|
|
|
260
257
|
- lib/zuora_observability/engine.rb
|
|
261
258
|
- lib/zuora_observability/env.rb
|
|
262
259
|
- lib/zuora_observability/logger.rb
|
|
260
|
+
- lib/zuora_observability/logging/custom_options.rb
|
|
263
261
|
- lib/zuora_observability/logging/formatter.rb
|
|
264
262
|
- lib/zuora_observability/metrics.rb
|
|
265
|
-
- lib/zuora_observability/metrics/point_value.rb
|
|
266
|
-
- lib/zuora_observability/metrics/telegraf.rb
|
|
267
263
|
- lib/zuora_observability/version.rb
|
|
268
|
-
homepage:
|
|
264
|
+
homepage:
|
|
269
265
|
licenses:
|
|
270
266
|
- MIT
|
|
271
267
|
metadata: {}
|
|
272
|
-
post_install_message:
|
|
268
|
+
post_install_message:
|
|
273
269
|
rdoc_options: []
|
|
274
270
|
require_paths:
|
|
275
271
|
- lib
|
|
@@ -277,15 +273,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
277
273
|
requirements:
|
|
278
274
|
- - ">="
|
|
279
275
|
- !ruby/object:Gem::Version
|
|
280
|
-
version: '2.
|
|
276
|
+
version: '2.5'
|
|
281
277
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
282
278
|
requirements:
|
|
283
|
-
- - "
|
|
279
|
+
- - ">="
|
|
284
280
|
- !ruby/object:Gem::Version
|
|
285
|
-
version:
|
|
281
|
+
version: '0'
|
|
286
282
|
requirements: []
|
|
287
|
-
rubygems_version: 3.
|
|
288
|
-
signing_key:
|
|
283
|
+
rubygems_version: 3.2.22
|
|
284
|
+
signing_key:
|
|
289
285
|
specification_version: 4
|
|
290
286
|
summary: Summary of ZuoraObservability.
|
|
291
287
|
test_files: []
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//= link_directory ../stylesheets/zuora_observability .css
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
*/
|
|
@@ -1,84 +0,0 @@
|
|
|
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
|
|
@@ -1,98 +0,0 @@
|
|
|
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
|