dogstatsd-ruby 4.4.0 → 4.8.1

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,67 @@
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
+ raise "Event #{title} payload is too big (more that 8KB), event discarded"
52
+ end
53
+ end
54
+ end
55
+
56
+ protected
57
+ attr_reader :tag_serializer
58
+
59
+ def escape(text)
60
+ text.delete('|').tap do |t|
61
+ t.gsub!("\n", '\n')
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ 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,62 @@
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
+ @tag_serializer = TagSerializer.new(global_tags)
10
+ end
11
+
12
+ def format(name, delta, type, tags: [], sample_rate: 1)
13
+ String.new.tap do |stat|
14
+ stat << prefix if prefix
15
+
16
+ # stat value
17
+ stat << formated_name(name)
18
+ stat << ':'
19
+ stat << delta.to_s
20
+
21
+ # stat type
22
+ stat << '|'
23
+ stat << type
24
+
25
+ # sample_rate
26
+ if sample_rate != 1
27
+ stat << '|'
28
+ stat << '@'
29
+ stat << sample_rate.to_s
30
+ end
31
+
32
+ # tags
33
+ if tags_list = tag_serializer.format(tags)
34
+ stat << '|'
35
+ stat << '#'
36
+ stat << tags_list
37
+ end
38
+ end
39
+ end
40
+
41
+ def global_tags
42
+ tag_serializer.global_tags
43
+ end
44
+
45
+ private
46
+ attr_reader :prefix
47
+ attr_reader :tag_serializer
48
+
49
+ def formated_name(name)
50
+ formated = name.is_a?(String) ? name.dup : name.to_s
51
+
52
+ formated.tap do |f|
53
+ # replace Ruby module scoping with '.'
54
+ f.gsub!('::', '.')
55
+ # replace reserved chars (: | @) with underscores.
56
+ f.tr!(':|@', '_')
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,91 @@
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
+ end
17
+
18
+ def format(message_tags)
19
+ # fast return global tags if there's no message_tags
20
+ # to avoid more allocations
21
+ tag_list = if message_tags && message_tags.any?
22
+ global_tags + to_tags_list(message_tags)
23
+ else
24
+ global_tags
25
+ end
26
+
27
+ tag_list.join(',') if tag_list.any?
28
+ end
29
+
30
+ attr_reader :global_tags
31
+
32
+ private
33
+
34
+ def to_tags_hash(tags)
35
+ case tags
36
+ when Hash
37
+ tags.dup
38
+ when Array
39
+ Hash[
40
+ tags.map do |string|
41
+ tokens = string.split(':')
42
+ tokens << nil if tokens.length == 1
43
+ tokens.length == 2 ? tokens : nil
44
+ end.compact
45
+ ]
46
+ else
47
+ {}
48
+ end
49
+ end
50
+
51
+ def to_tags_list(tags)
52
+ case tags
53
+ when Hash
54
+ tags.each_with_object([]) do |tag_pair, formatted_tags|
55
+ if tag_pair.last.nil?
56
+ formatted_tags << "#{tag_pair.first}"
57
+ else
58
+ formatted_tags << "#{tag_pair.first}:#{tag_pair.last}"
59
+ end
60
+ end
61
+ when Array
62
+ tags.dup
63
+ else
64
+ []
65
+ end.map! do |tag|
66
+ escape_tag_content(tag)
67
+ end
68
+ end
69
+
70
+ def escape_tag_content(tag)
71
+ tag.to_s.delete('|,')
72
+ end
73
+
74
+ def dd_tags(env = ENV)
75
+ return {} unless dd_tags = env['DD_TAGS']
76
+
77
+ to_tags_hash(dd_tags.split(','))
78
+ end
79
+
80
+ def default_tags(env = ENV)
81
+ dd_tags(env).tap do |tags|
82
+ tags['dd.internal.entity_id'] = env['DD_ENTITY_ID'] if env.key?('DD_ENTITY_ID')
83
+ tags['env'] = env['DD_ENV'] if env.key?('DD_ENV')
84
+ tags['service'] = env['DD_SERVICE'] if env.key?('DD_SERVICE')
85
+ tags['version'] = env['DD_VERSION'] if env.key?('DD_VERSION')
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end