dogstatsd-ruby 4.6.0 → 4.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Batch
6
+ def initialize(connection, max_buffer_bytes)
7
+ @connection = connection
8
+ @max_buffer_bytes = max_buffer_bytes
9
+ @depth = 0
10
+ reset
11
+ end
12
+
13
+ def open
14
+ @depth += 1
15
+
16
+ yield
17
+ ensure
18
+ @depth -= 1
19
+ flush if !open?
20
+ end
21
+
22
+ def open?
23
+ @depth > 0
24
+ end
25
+
26
+ def add(message)
27
+ message_bytes = message.bytesize
28
+
29
+ unless @buffer_bytes == 0
30
+ if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
31
+ flush
32
+ else
33
+ @buffer << "\n"
34
+ @buffer_bytes += 1
35
+ end
36
+ end
37
+
38
+ @buffer << message
39
+ @buffer_bytes += message_bytes
40
+ end
41
+
42
+ def flush
43
+ return if @buffer_bytes == 0
44
+ @connection.write(@buffer)
45
+ reset
46
+ end
47
+
48
+ private
49
+
50
+ def reset
51
+ @buffer = String.new
52
+ @buffer_bytes = 0
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ class Connection
6
+ def initialize(telemetry)
7
+ @telemetry = telemetry
8
+ end
9
+
10
+ # Close the underlying socket
11
+ def close
12
+ begin
13
+ @socket && @socket.close if instance_variable_defined?(:@socket)
14
+ rescue StandardError => boom
15
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
16
+ end
17
+ @socket = nil
18
+ end
19
+
20
+ def write(payload)
21
+ logger.debug { "Statsd: #{payload}" } if logger
22
+
23
+ flush_telemetry = telemetry.flush?
24
+
25
+ payload += telemetry.flush if flush_telemetry
26
+
27
+ send_message(payload)
28
+
29
+ telemetry.reset if flush_telemetry
30
+
31
+ telemetry.sent(packets: 1, bytes: payload.length)
32
+ rescue StandardError => boom
33
+ # Try once to reconnect if the socket has been closed
34
+ retries ||= 1
35
+ if retries <= 1 &&
36
+ (boom.is_a?(Errno::ENOTCONN) or
37
+ boom.is_a?(Errno::ECONNREFUSED) or
38
+ boom.is_a?(IOError) && boom.message =~ /closed stream/i)
39
+ retries += 1
40
+ begin
41
+ close
42
+ retry
43
+ rescue StandardError => e
44
+ boom = e
45
+ end
46
+ end
47
+
48
+ telemetry.dropped(packets: 1, bytes: payload.length)
49
+ logger.error { "Statsd: #{boom.class} #{boom}" } if logger
50
+ nil
51
+ end
52
+
53
+ private
54
+ attr_reader :telemetry
55
+ attr_reader :logger
56
+
57
+ def socket
58
+ @socket ||= connect
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'serialization/tag_serializer'
11
+ require_relative 'serialization/service_check_serializer'
12
+ require_relative 'serialization/event_serializer'
13
+ require_relative 'serialization/stat_serializer'
14
+
15
+ require_relative 'serialization/serializer'
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class EventSerializer
7
+ EVENT_BASIC_OPTIONS = {
8
+ date_happened: 'd:',
9
+ hostname: 'h:',
10
+ aggregation_key: 'k:',
11
+ priority: 'p:',
12
+ source_type_name: 's:',
13
+ alert_type: 't:',
14
+ }.freeze
15
+
16
+ def initialize(global_tags: [])
17
+ @tag_serializer = TagSerializer.new(global_tags)
18
+ end
19
+
20
+ def format(title, text, options = EMPTY_OPTIONS)
21
+ title = escape(title)
22
+ text = escape(text)
23
+
24
+ String.new.tap do |event|
25
+ event << '_e{'
26
+ event << title.bytesize.to_s
27
+ event << ','
28
+ event << text.bytesize.to_s
29
+ event << '}:'
30
+ event << title
31
+ event << '|'
32
+ event << text
33
+
34
+ # we are serializing the generic service check options
35
+ # before serializing specialized options that need edge-cases
36
+ EVENT_BASIC_OPTIONS.each do |option_key, shortcut|
37
+ if value = options[option_key]
38
+ event << '|'
39
+ event << shortcut
40
+ event << value.to_s.delete('|')
41
+ end
42
+ end
43
+
44
+ # also returns the global tags from serializer
45
+ if tags = tag_serializer.format(options[:tags])
46
+ event << '|#'
47
+ event << tags
48
+ end
49
+
50
+ if event.bytesize > MAX_EVENT_SIZE
51
+ if options[:truncate_if_too_long]
52
+ event.slice!(MAX_EVENT_SIZE..event.length)
53
+ else
54
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ protected
61
+ attr_reader :tag_serializer
62
+
63
+ def escape(text)
64
+ text.delete('|').tap do |t|
65
+ t.gsub!("\n", '\n')
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'forwardable'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ module Serialization
8
+ class Serializer
9
+ def initialize(prefix: nil, global_tags: [])
10
+ @stat_serializer = StatSerializer.new(prefix, global_tags: global_tags)
11
+ @service_check_serializer = ServiceCheckSerializer.new(global_tags: global_tags)
12
+ @event_serializer = EventSerializer.new(global_tags: global_tags)
13
+ end
14
+
15
+ # using *args would make new allocations
16
+ def to_stat(name, delta, type, tags: [], sample_rate: 1)
17
+ stat_serializer.format(name, delta, type, tags: tags, sample_rate: sample_rate)
18
+ end
19
+
20
+ # using *args would make new allocations
21
+ def to_service_check(name, status, options = EMPTY_OPTIONS)
22
+ service_check_serializer.format(name, status, options)
23
+ end
24
+
25
+ # using *args would make new allocations
26
+ def to_event(title, text, options = EMPTY_OPTIONS)
27
+ event_serializer.format(title, text, options)
28
+ end
29
+
30
+ def global_tags
31
+ stat_serializer.global_tags
32
+ end
33
+
34
+ protected
35
+ attr_reader :stat_serializer
36
+ attr_reader :service_check_serializer
37
+ attr_reader :event_serializer
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class ServiceCheckSerializer
7
+ SERVICE_CHECK_BASIC_OPTIONS = {
8
+ timestamp: 'd:',
9
+ hostname: 'h:',
10
+ }.freeze
11
+
12
+ def initialize(global_tags: [])
13
+ @tag_serializer = TagSerializer.new(global_tags)
14
+ end
15
+
16
+ def format(name, status, options = EMPTY_OPTIONS)
17
+ String.new.tap do |service_check|
18
+ # line basics
19
+ service_check << "_sc"
20
+ service_check << "|"
21
+ service_check << name.to_s
22
+ service_check << "|"
23
+ service_check << status.to_s
24
+
25
+ # we are serializing the generic service check options
26
+ # before serializing specialized options that need edge-cases
27
+ SERVICE_CHECK_BASIC_OPTIONS.each do |option_key, shortcut|
28
+ if value = options[option_key]
29
+ service_check << '|'
30
+ service_check << shortcut
31
+ service_check << value.to_s.delete('|')
32
+ end
33
+ end
34
+
35
+ if message = options[:message]
36
+ service_check << '|m:'
37
+ service_check << escape_message(message)
38
+ end
39
+
40
+ # also returns the global tags from serializer
41
+ if tags = tag_serializer.format(options[:tags])
42
+ service_check << '|#'
43
+ service_check << tags
44
+ end
45
+ end
46
+ end
47
+
48
+ protected
49
+ attr_reader :tag_serializer
50
+
51
+ def escape_message(message)
52
+ message.delete('|').tap do |m|
53
+ m.gsub!("\n", '\n')
54
+ m.gsub!('m:', 'm\:')
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class StatSerializer
7
+ def initialize(prefix, global_tags: [])
8
+ @prefix = prefix
9
+ @prefix_str = prefix.to_s
10
+ @tag_serializer = TagSerializer.new(global_tags)
11
+ end
12
+
13
+ def format(name, delta, type, tags: [], sample_rate: 1)
14
+ name = formated_name(name)
15
+
16
+ if sample_rate != 1
17
+ if tags_list = tag_serializer.format(tags)
18
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}|##{tags_list}"
19
+ else
20
+ "#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}"
21
+ end
22
+ else
23
+ if tags_list = tag_serializer.format(tags)
24
+ "#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
25
+ else
26
+ "#{@prefix_str}#{name}:#{delta}|#{type}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def global_tags
32
+ tag_serializer.global_tags
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :prefix
38
+ attr_reader :tag_serializer
39
+
40
+ def formated_name(name)
41
+ if name.is_a?(String)
42
+ # DEV: gsub is faster than dup.gsub!
43
+ formated = name.gsub('::', '.')
44
+ else
45
+ formated = name.to_s
46
+ formated.gsub!('::', '.')
47
+ end
48
+
49
+ formated.tr!(':|@', '_')
50
+ formated
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ class Statsd
5
+ module Serialization
6
+ class TagSerializer
7
+ def initialize(global_tags = [], env = ENV)
8
+ # Convert to hash
9
+ global_tags = to_tags_hash(global_tags)
10
+
11
+ # Merge with default tags
12
+ global_tags = default_tags(env).merge(global_tags)
13
+
14
+ # Convert to tag list and set
15
+ @global_tags = to_tags_list(global_tags)
16
+ @global_tags_formatted = @global_tags.join(',') if @global_tags.any?
17
+ end
18
+
19
+ def format(message_tags)
20
+ if !message_tags || message_tags.empty?
21
+ return @global_tags_formatted
22
+ end
23
+
24
+ tags = if @global_tags_formatted
25
+ [@global_tags_formatted, to_tags_list(message_tags)]
26
+ else
27
+ to_tags_list(message_tags)
28
+ end
29
+
30
+ tags.join(',')
31
+ end
32
+
33
+ attr_reader :global_tags
34
+
35
+ private
36
+
37
+ def to_tags_hash(tags)
38
+ case tags
39
+ when Hash
40
+ tags.dup
41
+ when Array
42
+ Hash[
43
+ tags.map do |string|
44
+ tokens = string.split(':')
45
+ tokens << nil if tokens.length == 1
46
+ tokens.length == 2 ? tokens : nil
47
+ end.compact
48
+ ]
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ def to_tags_list(tags)
55
+ case tags
56
+ when Hash
57
+ tags.map do |name, value|
58
+ if value
59
+ escape_tag_content("#{name}:#{value}")
60
+ else
61
+ escape_tag_content(name)
62
+ end
63
+ end
64
+ when Array
65
+ tags.map { |tag| escape_tag_content(tag) }
66
+ else
67
+ []
68
+ end
69
+ end
70
+
71
+ def escape_tag_content(tag)
72
+ tag.to_s.delete('|,')
73
+ end
74
+
75
+ def dd_tags(env = ENV)
76
+ return {} unless dd_tags = env['DD_TAGS']
77
+
78
+ to_tags_hash(dd_tags.split(','))
79
+ end
80
+
81
+ def default_tags(env = ENV)
82
+ dd_tags(env).tap do |tags|
83
+ tags['dd.internal.entity_id'] = env['DD_ENTITY_ID'] if env.key?('DD_ENTITY_ID')
84
+ tags['env'] = env['DD_ENV'] if env.key?('DD_ENV')
85
+ tags['service'] = env['DD_SERVICE'] if env.key?('DD_SERVICE')
86
+ tags['version'] = env['DD_VERSION'] if env.key?('DD_VERSION')
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end