├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── fluent-plugin-dogstatsd.gemspec
├── lib
└── fluent
│ └── plugin
│ ├── dogstatsd.rb
│ ├── dogstatsd
│ └── version.rb
│ └── out_dogstatsd.rb
└── test
├── plugin
└── test_out_dogstatsd.rb
└── test_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | *.bundle
19 | *.so
20 | *.o
21 | *.a
22 | mkmf.log
23 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in fluent-plugin-dogstatsd.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Ryota Arai
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fluent-plugin-dogstatsd
2 |
3 | Fluend plugin for Dogstatsd, that is statsd server for Datadog.
4 |
5 | ## Installation
6 |
7 | $ gem install fluent-plugin-dogstatsd
8 |
9 | ## Usage
10 |
11 | ```
12 | $ echo '{"type": "increment", "key": "apache.requests", "tags": {"url": "/"}}' | fluent-cat dogstatsd.hello
13 | $ echo '{"type": "histogram", "key": "apache.response_time", "value": 10.5, "tags": {"url": "/hello"}}' | fluent-cat dogstatsd.hello
14 | $ echo '{"type": "event", "title": "Deploy", "text": "New revision"}' | fluent-cat dogstatsd.hello
15 | ```
16 |
17 | Supported types are `increment`, `decrement`, `count`, `gauge`, `histogram`, `timing`, `set` and `event`.
18 |
19 | ## Configuration
20 |
21 | ```
22 |
23 | type dogstatsd
24 |
25 | # Dogstatsd host
26 | host localhost
27 |
28 | # Dogstatsd port
29 | port 8125
30 |
31 | # Use tag of fluentd record as key sent to Dogstatsd
32 | use_tag_as_key false
33 |
34 | # (Treat fields in a record as tags)
35 | # flat_tags true
36 |
37 | # (Metric type in Datadog.)
38 | # metric_type increment
39 |
40 | # Default: "value"
41 | # value_key Value
42 |
43 | ```
44 |
45 | ## Example
46 |
47 | ### Count log lines
48 |
49 | ```apache
50 |
51 | type tail
52 | path /tmp/sample.log
53 | tag datadog.increment.sample
54 | format ...
55 |
56 |
57 |
58 | type dogstatsd
59 | metric_type increment
60 | flat_tags true
61 | use_tag_as_key true
62 |
63 | ```
64 |
65 | ### Histogram
66 |
67 | ```apache
68 |
69 | type tail
70 | path /tmp/sample.log
71 | tag datadog.histogram.sample
72 | format /^(?[^ ]*) (?[^ ]*)$/
73 |
74 |
75 |
76 | type dogstatsd
77 | metric_type histogram
78 | flat_tags true
79 | use_tag_as_key true
80 |
81 | ```
82 |
83 | ### MySQL threads
84 |
85 | ```apache
86 |
87 | type mysql_query
88 | tag datadog.histogram.mysql_threads
89 | query SHOW VARIABLES LIKE 'Thread_%'
90 |
91 |
92 |
93 | type dogstatsd
94 | metric_type histogram
95 | value_key Value
96 | flat_tags true
97 | use_tag_as_key true
98 |
99 | ```
100 |
101 | ## Contributing
102 |
103 | 1. Fork it ( https://github.com/ryotarai/fluent-plugin-dogstatsd/fork )
104 | 2. Create your feature branch (`git checkout -b my-new-feature`)
105 | 3. Commit your changes (`git commit -am 'Add some feature'`)
106 | 4. Push to the branch (`git push origin my-new-feature`)
107 | 5. Create a new Pull Request
108 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | require 'rake/testtask'
4 |
5 | Rake::TestTask.new(:test) do |test|
6 | test.libs << 'test'
7 | test.test_files = FileList['test/plugin/*.rb']
8 | end
9 |
10 |
--------------------------------------------------------------------------------
/fluent-plugin-dogstatsd.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'fluent/plugin/dogstatsd/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "fluent-plugin-dogstatsd"
8 | spec.version = Fluent::Plugin::Dogstatsd::VERSION
9 | spec.authors = ["Ryota Arai"]
10 | spec.email = ["ryota.arai@gmail.com"]
11 | spec.summary = %q{Fluent plugin for Dogstatsd, that is statsd server for Datadog.}
12 | spec.homepage = "https://github.com/ryotarai/fluent-plugin-dogstatsd"
13 | spec.license = "MIT"
14 |
15 | spec.files = `git ls-files -z`.split("\x0")
16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18 | spec.require_paths = ["lib"]
19 |
20 | spec.add_dependency "fluentd"
21 | spec.add_dependency "dogstatsd-ruby", "~> 1.4.1"
22 |
23 | spec.add_development_dependency "bundler", "~> 1.6"
24 | spec.add_development_dependency "rake"
25 | spec.add_development_dependency "test-unit"
26 | end
27 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/dogstatsd.rb:
--------------------------------------------------------------------------------
1 | require "fluent/plugin/dogstatsd/version"
2 |
3 | module Fluent
4 | module Plugin
5 | module Dogstatsd
6 | # Your code goes here...
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/dogstatsd/version.rb:
--------------------------------------------------------------------------------
1 | module Fluent
2 | module Plugin
3 | module Dogstatsd
4 | VERSION = "0.0.6"
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/out_dogstatsd.rb:
--------------------------------------------------------------------------------
1 | module Fluent
2 | class DogstatsdOutput < BufferedOutput
3 | Plugin.register_output('dogstatsd', self)
4 |
5 | config_param :host, :string, :default => nil
6 | config_param :port, :integer, :default => nil
7 | config_param :use_tag_as_key, :bool, :default => false
8 | config_param :use_tag_as_key_if_missing, :bool, :default => false
9 | config_param :flat_tags, :bool, :default => false
10 | config_param :flat_tag, :bool, :default => false # obsolete
11 | config_param :metric_type, :string, :default => nil
12 | config_param :value_key, :string, :default => nil
13 | config_param :sample_rate, :float, :default => nil
14 |
15 | unless method_defined?(:log)
16 | define_method(:log) { $log }
17 | end
18 |
19 | attr_accessor :statsd
20 |
21 | def initialize
22 | super
23 |
24 | require 'statsd' # dogstatsd-ruby
25 | end
26 |
27 | def start
28 | super
29 |
30 | host = @host || Statsd::DEFAULT_HOST
31 | port = @port || Statsd::DEFAULT_PORT
32 |
33 | @statsd ||= Statsd.new(host, port)
34 | end
35 |
36 | def format(tag, time, record)
37 | [tag, time, record].to_msgpack
38 | end
39 |
40 | def write(chunk)
41 | @statsd.batch do |s|
42 | chunk.msgpack_each do |tag, time, record|
43 | key = if @use_tag_as_key
44 | tag
45 | else
46 | record.delete('key')
47 | end
48 |
49 | if !key && @use_tag_as_key_if_missing
50 | key = tag
51 | end
52 |
53 | unless key
54 | log.warn "'key' is not specified. skip this record:", tag: tag
55 | next
56 | end
57 |
58 | value = record.delete(@value_key || 'value')
59 |
60 | options = {}
61 | title = record.delete('title')
62 | text = record.delete('text')
63 | type = @metric_type || record.delete('type')
64 | sample_rate = @sample_rate || record.delete('sample_rate')
65 |
66 | if sample_rate
67 | options[:sample_rate] = sample_rate
68 | end
69 |
70 | tags = if @flat_tags || @flat_tag
71 | record
72 | else
73 | record['tags']
74 | end
75 | if tags
76 | options[:tags] = tags.map do |k, v|
77 | "#{k}:#{v}"
78 | end
79 | end
80 |
81 | case type
82 | when 'increment'
83 | s.increment(key, options)
84 | when 'decrement'
85 | s.decrement(key, options)
86 | when 'count'
87 | s.count(key, value, options)
88 | when 'gauge'
89 | s.gauge(key, value, options)
90 | when 'histogram'
91 | s.histogram(key, value, options)
92 | when 'timing'
93 | s.timing(key, value, options)
94 | when 'set'
95 | s.set(key, value, options)
96 | when 'event'
97 | options[:alert_type] = record['alert_type']
98 | s.event(title, text, options)
99 | when nil
100 | log.warn "type is not provided (You can provide type via `metric_type` in config or `type` field in a record."
101 | else
102 | log.warn "Type '#{type}' is unknown."
103 | end
104 | end
105 | end
106 | end
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/test/plugin/test_out_dogstatsd.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DummyStatsd
4 | attr_reader :messages
5 |
6 | def initialize
7 | @messages = []
8 | end
9 |
10 | def batch
11 | yield(self)
12 | end
13 |
14 | %i!increment decrement count gauge histogram timing set event!.each do |name|
15 | define_method(name) do |*args|
16 | @messages << [name, args].flatten
17 | end
18 | end
19 | end
20 |
21 | class DogstatsdOutputTest < Test::Unit::TestCase
22 | def setup
23 | Fluent::Test.setup
24 | require 'fluent/plugin/out_dogstatsd'
25 | end
26 |
27 | def teardown
28 | end
29 |
30 | def test_configure
31 | d = create_driver(<<-EOC)
32 | type dogstatsd
33 | host HOST
34 | port 12345
35 | EOC
36 |
37 | assert_equal('HOST', d.instance.host)
38 | assert_equal(12345, d.instance.port)
39 | end
40 |
41 | def test_write
42 | d = create_driver
43 |
44 | d.emit({'type' => 'increment', 'key' => 'hello.world1'}, Time.now.to_i)
45 | d.emit({'type' => 'increment', 'key' => 'hello.world2'}, Time.now.to_i)
46 | d.emit({'type' => 'decrement', 'key' => 'hello.world'}, Time.now.to_i)
47 | d.emit({'type' => 'count', 'value' => 10, 'key' => 'hello.world'}, Time.now.to_i)
48 | d.emit({'type' => 'gauge', 'value' => 10, 'key' => 'hello.world'}, Time.now.to_i)
49 | d.emit({'type' => 'histogram', 'value' => 10, 'key' => 'hello.world'}, Time.now.to_i)
50 | d.emit({'type' => 'timing', 'value' => 10, 'key' => 'hello.world'}, Time.now.to_i)
51 | d.emit({'type' => 'set', 'value' => 10, 'key' => 'hello.world'}, Time.now.to_i)
52 | d.emit({'type' => 'event', 'title' => 'Deploy', 'text' => 'Revision', 'key' => 'hello.world'}, Time.now.to_i)
53 | d.run
54 |
55 | assert_equal(d.instance.statsd.messages, [
56 | [:increment, 'hello.world1', {}],
57 | [:increment, 'hello.world2', {}],
58 | [:decrement, 'hello.world', {}],
59 | [:count, 'hello.world', 10, {}],
60 | [:gauge, 'hello.world', 10, {}],
61 | [:histogram, 'hello.world', 10, {}],
62 | [:timing, 'hello.world', 10, {}],
63 | [:set, 'hello.world', 10, {}],
64 | [:event, 'Deploy', 'Revision', {}],
65 | ])
66 | end
67 |
68 | def test_flat_tag
69 | d = create_driver(<<-EOC)
70 | #{default_config}
71 | flat_tag true
72 | EOC
73 |
74 | d.emit({'type' => 'increment', 'key' => 'hello.world', 'tagKey' => 'tagValue'}, Time.now.to_i)
75 | d.run
76 |
77 | assert_equal(d.instance.statsd.messages, [
78 | [:increment, 'hello.world', {tags: ["tagKey:tagValue"]}],
79 | ])
80 | end
81 |
82 | def test_metric_type
83 | d = create_driver(<<-EOC)
84 | #{default_config}
85 | metric_type decrement
86 | EOC
87 |
88 | d.emit({'key' => 'hello.world', 'tags' => {'tagKey' => 'tagValue'}}, Time.now.to_i)
89 | d.run
90 |
91 | assert_equal(d.instance.statsd.messages, [
92 | [:decrement, 'hello.world', {tags: ["tagKey:tagValue"]}],
93 | ])
94 | end
95 |
96 | def test_use_tag_as_key
97 | d = create_driver(<<-EOC)
98 | #{default_config}
99 | use_tag_as_key true
100 | EOC
101 |
102 | d.emit({'type' => 'increment'}, Time.now.to_i)
103 | d.run
104 |
105 | assert_equal(d.instance.statsd.messages, [
106 | [:increment, 'dogstatsd.tag', {}],
107 | ])
108 | end
109 |
110 | def test_use_tag_as_key_fallback
111 | d = create_driver(<<-EOC)
112 | #{default_config}
113 | use_tag_as_key_if_missing true
114 | EOC
115 |
116 | d.emit({'type' => 'increment'}, Time.now.to_i)
117 | d.run
118 |
119 | assert_equal(d.instance.statsd.messages, [
120 | [:increment, 'dogstatsd.tag', {}],
121 | ])
122 | end
123 |
124 | def test_tags
125 | d = create_driver
126 | d.emit({'type' => 'increment', 'key' => 'hello.world', 'tags' => {'key' => 'value'}}, Time.now.to_i)
127 | d.run
128 |
129 | assert_equal(d.instance.statsd.messages, [
130 | [:increment, 'hello.world', {tags: ["key:value"]}],
131 | ])
132 | end
133 |
134 | def test_sample_rate_config
135 | d = create_driver(<<-EOC)
136 | #{default_config}
137 | sample_rate .5
138 | EOC
139 |
140 | d.emit({'type' => 'increment', 'key' => 'tag'}, Time.now.to_i)
141 | d.run
142 |
143 | assert_equal(d.instance.statsd.messages, [
144 | [:increment, 'tag', {sample_rate: 0.5}],
145 | ])
146 | end
147 |
148 | def test_sample_rate
149 | d = create_driver
150 | d.emit({'type' => 'increment', 'sample_rate' => 0.5, 'key' => 'tag'}, Time.now.to_i)
151 | d.run
152 |
153 | assert_equal(d.instance.statsd.messages, [
154 | [:increment, 'tag', {sample_rate: 0.5}],
155 | ])
156 | end
157 |
158 | private
159 | def default_config
160 | <<-EOC
161 | type dogstatsd
162 | EOC
163 | end
164 |
165 | def create_driver(conf = default_config)
166 | Fluent::Test::BufferedOutputTestDriver.new(Fluent::DogstatsdOutput, 'dogstatsd.tag').configure(conf).tap do |d|
167 | d.instance.statsd = DummyStatsd.new
168 | end
169 | end
170 | end
171 |
172 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 | require 'fluent/test'
3 |
4 |
--------------------------------------------------------------------------------