fluent-plugin-mixpanel-enchanced 0.0.10

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27467730f8f9dcfa247c004aa9ce3e0ca374f993
4
+ data.tar.gz: 71c01ebc5f14ee468f6685c420db82bbd2cb3d64
5
+ SHA512:
6
+ metadata.gz: 12c4377d98a3a4b57d75583fa8689fd7c0a79706a2d6c035bf01f887b9b392d655f4da36fcc445eec2aa1c66eeea86eed52011d26d928cdec55e091399935e9f
7
+ data.tar.gz: 9be23b64240e8e057885519a656a4cb2bd09f095f09ce8210705282702b23cbcbfa8dc355ebc2b2097d061c0ccb00864bd4516c4de91467c40c1d3fa218dc492
data/.gitignore ADDED
@@ -0,0 +1,18 @@
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
+ .idea/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.9
4
+ - 2.2.5
5
+ - 2.3.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-mixpanel.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2014- Kazuyuki Honda
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # fluent-plugin-mixpanel
2
+
3
+ [![Build Status](https://travis-ci.org/hakobera/fluent-plugin-mixpanel.png?branch=master)](https://travis-ci.org/hakobera/fluent-plugin-mixpanel)
4
+
5
+ **CAUTION** This plugin does not support Ruby < 2.0.
6
+
7
+ ## Component
8
+
9
+ ### MixpanelOutput
10
+
11
+ [Fluentd](http://fluentd.org) plugin to send event track data to [mixpanel](https://mixpanel.com).
12
+
13
+ ### HttpMixpanelInput
14
+
15
+ [Fluentd](http://fluentd.org) plugin to integrate [mixpanel javascript libraries](https://mixpanel.com/docs/integration-libraries/javascript).
16
+
17
+ ## Installation
18
+
19
+ Install with gem or fluent-gem command as:
20
+
21
+ ```
22
+ # for fluentd
23
+ $ gem install fluent-plugin-mixpanel
24
+
25
+ # for td-agent
26
+ $ sudo /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-mixpanel
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ ### MixpanelOutput
32
+
33
+ MixpanelOutput needs mixpanel's `project_token`, that can get from your mixpanel project settings.
34
+
35
+ #### Use distinct_id_key and event_key
36
+
37
+ You should also specify property key name by `distinct_id_key` and `event_key`.
38
+
39
+ ```
40
+ <match output.mixpanel.*>
41
+ type mixpanel
42
+ project_token YOUR_PROJECT_TOKEN
43
+ distinct_id_key user_id
44
+ event_key event_name
45
+ </match>
46
+ ```
47
+
48
+ If record like this:
49
+
50
+ ```rb
51
+ { user_id: "123", event_name: "event1", key1: "value1", key2: "value2" }
52
+ ```
53
+
54
+ above settings send to the following data to mixpanel, using [mixpanel-ruby](https://github.com/mixpanel/mixpanel-ruby) gem.
55
+
56
+ ```rb
57
+ tracker = Mixpanel::Tracker.new(YOUR_PROJECT_TOKEN)
58
+ tracker.track("123", "event1", { key1: "value1", key2: "value2" })
59
+ ```
60
+
61
+ #### Use distinct_id_key and event_map_tag
62
+
63
+ You can use tag name as event name like this. (see additional tag manipulations options below)
64
+
65
+ #### Discarding mixpanel errors
66
+
67
+ When delivering events to Mixpanel, Fluent creates a chunk of messages to send. By default, if one event fails to send to Mixpanel, all messages in that chunk are requeued for delivery. Enabling `discard_event_on_send_error` allows you to ignore single delivery failures. The event is logged via `info`, including the record being dropped.
68
+
69
+ ```
70
+ <match output.mixpanel.*>
71
+ ...
72
+ discard_event_on_send_error true
73
+ ...
74
+ </match>
75
+ ```
76
+
77
+ ##PLEASE NOTE (breaking api change in a future release)
78
+
79
+ The api for remove_tag_prefix will be changing in a future release. There is currently a boolean option,
80
+ use_legacy_prefix_behavior, which will ensure legacy behavior is maintained until that time. Eventually this option will go away
81
+ as well and the new behavior will be the only way. The difference is pretty simple, the '.' in the prefix needs to be specified.
82
+ This change allows this plugin to use Fluet's mixin and unifies syntax across plugins. Currently, use_legacy_prefix_behavior
83
+ defaults to true, which will work either way, but eventually you will need to specify the '.' in your prefix. Again, use_legacy_prefix_behavior simply removes any '.' along with the specified prefix and will behave properly even after you change your configs
84
+ to be current as seen below. You do not need to set this option.
85
+
86
+ ```
87
+ <match output.mixpanel.*>
88
+ type mixpanel
89
+ project_token YOUR_PROJECT_TOKEN
90
+ distinct_id_key user_id
91
+ remove_tag_prefix output.mixpanel.
92
+ event_map_tag true
93
+ </match>
94
+ ```
95
+
96
+ If tag name is `output.mixpanel.event1` and record like this:
97
+
98
+ ```rb
99
+ { user_id: "123", key1: "value1", key2: "value2" }
100
+ ```
101
+
102
+ above settings send to the following data to mixpanel, using [mixpanel-ruby](https://github.com/mixpanel/mixpanel-ruby) gem.
103
+
104
+ ```rb
105
+ tracker = Mixpanel::Tracker.new(YOUR_PROJECT_TOKEN)
106
+ tracker.track("123", "event1", { key1: "value1", key2: "value2" })
107
+ ```
108
+
109
+ #### Use the import method to post instead of track
110
+
111
+ You can use tag name as event name like this.
112
+
113
+ ```
114
+ <match output.mixpanel.*>
115
+ type mixpanel
116
+ project_token YOUR_PROJECT_TOKEN
117
+ distinct_id_key user_id
118
+ remove_tag_prefix output.mixpanel.
119
+ event_map_tag true
120
+ use_import true
121
+ api_key YOUR_API_KEY
122
+ </match>
123
+ ```
124
+
125
+ If tag name is `output.mixpanel.event1` and record like this:
126
+
127
+ ```rb
128
+ { user_id: "123", key1: "value1", key2: "value2" }
129
+ ```
130
+
131
+ above settings send to the following data to mixpanel, using [mixpanel-ruby](https://github.com/mixpanel/mixpanel-ruby) gem.
132
+
133
+ ```rb
134
+ tracker = Mixpanel::Tracker.new(YOUR_PROJECT_TOKEN)
135
+ tracker.import(api_key, "123", "event1", { key1: "value1", key2: "value2" })
136
+ ```
137
+
138
+ ---
139
+
140
+ fluentd-plugin-mixpanel also includes the HandleTagNameMixin mixin which allows the following additional options:
141
+
142
+ ```
143
+ remove_tag_prefix <tag_prefix_to_remove_including_the_dot>
144
+ remove_tag_suffix <tag_suffix_to_remove_including_the_dot>
145
+ add_tag_prefix <tag_prefix_to_add_including_the_dot>
146
+ add_tag_suffix <tag_suffix_to_add_including_the_dot>
147
+ ```
148
+
149
+ ### HttpMixpanelInput
150
+
151
+ HttpMixpanelInput has same configuration as [http Input Plugin](http://docs.fluentd.org/en/articles/in_http).
152
+
153
+ ```
154
+ <source>
155
+ type http_mixpanel
156
+ bind 127.0.0.1
157
+ port 8888
158
+ body_size_limit 10m
159
+ keepalive_timeout 5
160
+ add_http_headers true
161
+ </source>
162
+ ```
163
+
164
+ In example folder, you can see example configuration and HTML.
165
+
166
+ ## Contributing
167
+
168
+ 1. Fork it ( http://github.com/hakobera/fluent-plugin-mixpanel/fork )
169
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
170
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
171
+ 4. Push to the branch (`git push origin my-new-feature`)
172
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ ruby -rwebrick -e'WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => Dir.pwd).start'
@@ -0,0 +1,12 @@
1
+ <source>
2
+ type http_mixpanel
3
+ bind 127.0.0.1
4
+ port 8888
5
+ body_size_limit 10m
6
+ keepalive_timeout 5
7
+ add_http_headers true
8
+ </source>
9
+
10
+ <match mixpanel.**>
11
+ type stdout
12
+ </match>
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>in_http_mixpanel example</title>
6
+ </head>
7
+ <body>
8
+ <ul>
9
+ <li><button data-event='event1' data-value="1">Event1</button></li>
10
+ <li><button data-event='event2' data-value="2">Event2</button></li>
11
+ <li><button data-event='event3' data-value="3">Event3</button></li>
12
+ </ul>
13
+ <script type="text/javascript">
14
+ (function(c,a){window.mixpanel=a;var b,d,h,e;b=c.createElement("script");
15
+ b.type="text/javascript";b.async=!0;b.src=("https:"===c.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';d=c.getElementsByTagName("script")[0];d.parentNode.insertBefore(b,d);a._i=[];a.init=function(b,c,f){function d(a,b){var c=b.split(".");2==c.length&&(a=a[c[0]],b=c[1]);a[b]=function(){a.push([b].concat(Array.prototype.slice.call(arguments,0)))}}var g=a;"undefined"!==typeof f?g=a[f]=[]:f="mixpanel";g.people=g.people||[];h=['disable','track','track_pageview','track_links','track_forms','register','register_once','unregister','identify','alias','name_tag','set_config','people.set','people.set_once','people.increment','people.track_charge','people.append'];for(e=0;e<h.length;e++)d(g,h[e]);a._i.push([b,c,f])};a.__SV=1.2;})(document,window.mixpanel||[]);
16
+ mixpanel.init("dummy", {
17
+ api_host: '//0.0.0.0:5000',
18
+ debug: true
19
+ });
20
+ </script>
21
+ <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
22
+ <script>
23
+ $(function () {
24
+ $('button').on('click', function () {
25
+ var evt = $(this).data('event');
26
+ var value = $(this).data('value');
27
+ mixpanel.track(evt, { value: value });
28
+ });
29
+ });
30
+ </script>
31
+ </body>
32
+ </html>
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-mixpanel-enchanced"
7
+ spec.version = "0.0.10"
8
+ spec.authors = ["zhron4x"]
9
+ spec.email = ["zhr0n4x@gmail.com"]
10
+ spec.summary = %q{Fluentd plugin to input/output event track data to mixpanel}
11
+ spec.description = %q{Fluentd plugin to input/output event track data to mixpanel}
12
+ spec.homepage = "https://github.com/zhron4x/fluent-plugin-mixpanel-enchanced"
13
+ spec.license = "Apache License, Version 2.0"
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_runtime_dependency "fluentd", ">= 0.10.55"
21
+ spec.add_runtime_dependency "mixpanel-ruby", "~> 2.2.0"
22
+
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "webmock"
25
+ spec.add_development_dependency "test-unit", "~> 3.0.2"
26
+ spec.add_development_dependency "pry"
27
+ end
@@ -0,0 +1,33 @@
1
+ require 'fluent/plugin/in_http'
2
+ require 'base64'
3
+ require 'pry'
4
+
5
+ class Fluent::HttpMixpanelInput < Fluent::HttpInput
6
+ Fluent::Plugin.register_input('http_mixpanel', self)
7
+
8
+ config_param :tag_prefix, :default => 'mixpanel'
9
+
10
+ def on_request(path_info, params)
11
+ data = Base64.decode64(params['data']).force_encoding('utf-8')
12
+ json = JSON.parse(data)
13
+ binding.pry
14
+ props = json['properties']
15
+ path = "/#{tag_prefix}.#{json['event']}"
16
+ params['json'] = props.to_json
17
+ params['time'] = props['time'].to_s if props['time']
18
+
19
+ ret = super(path, params)
20
+
21
+ headers = {
22
+ 'Access-Control-Allow-Credentials' => true,
23
+ 'Access-Control-Allow-Headers' => 'X-Requested-With',
24
+ 'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
25
+ 'Access-Control-Allow-Origin' => params['HTTP_ORIGIN'],
26
+ 'Access-Control-Max-Age' => 1728000,
27
+ 'Cache-Control' => 'no-cache, no-store',
28
+ 'Content-type' => 'text/plain'
29
+ }
30
+
31
+ [ret[0], headers, (ret[0] == '200 OK' ? '1' : '0')]
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ require 'mixpanel-ruby'
2
+
3
+ class Fluent::MixpanelOutputErrorHandler < Mixpanel::ErrorHandler
4
+ def initialize(logger)
5
+ @logger = logger
6
+ end
7
+
8
+ def handle(error)
9
+ # Default behavior is to not return an error. Mixpanel-ruby gem returns
10
+ # true/false. If there is an error, an optional error handler is called.
11
+ # In this case, here, we only want to log the error for future development
12
+ # of error handling.
13
+ @logger.error "MixpanelOutputErrorHandler:\n\tClass: #{error.class.to_s}\n\tMessage: #{error.message}\n\tBacktrace: #{error.backtrace}"
14
+ end
15
+ end
@@ -0,0 +1,127 @@
1
+ require_relative "mixpanel_ruby_error_handler.rb"
2
+
3
+ class Fluent::MixpanelOutput < Fluent::BufferedOutput
4
+ Fluent::Plugin.register_output('mixpanel', self)
5
+
6
+ include Fluent::HandleTagNameMixin
7
+
8
+ config_param :project_token, :string, :secret => true
9
+ config_param :api_key, :string, :default => '', :secret => true
10
+ config_param :use_import, :bool, :default => nil
11
+ config_param :distinct_id_key, :string
12
+ config_param :event_key, :string, :default => nil
13
+ config_param :ip_key, :string, :default => nil
14
+ config_param :event_map_tag, :bool, :default => false
15
+ #NOTE: This will be removed in a future release. Please specify the '.' on any prefix
16
+ config_param :use_legacy_prefix_behavior, :default => true
17
+ config_param :discard_event_on_send_error, :default => false
18
+
19
+ class MixpanelError < StandardError
20
+ end
21
+
22
+ def initialize
23
+ super
24
+ require 'mixpanel-ruby'
25
+ end
26
+
27
+ def configure(conf)
28
+ super
29
+ @project_tokey = conf['project_token']
30
+ @distinct_id_key = conf['distinct_id_key']
31
+ @event_key = conf['event_key']
32
+ @ip_key = conf['ip_key']
33
+ @event_map_tag = conf['event_map_tag']
34
+ @api_key = conf['api_key']
35
+ @use_import = conf['use_import']
36
+ @use_legacy_prefix_behavior = conf['use_legacy_prefix_behavior']
37
+ @discard_event_on_send_error = conf['discard_event_on_send_error']
38
+
39
+ if @event_key.nil? and !@event_map_tag
40
+ raise Fluent::ConfigError, "'event_key' must be specifed when event_map_tag == false."
41
+ end
42
+ end
43
+
44
+ def start
45
+ super
46
+ error_handler = Fluent::MixpanelOutputErrorHandler.new(log)
47
+ @tracker = Mixpanel::Tracker.new(@project_token, error_handler)
48
+ end
49
+
50
+ def shutdown
51
+ super
52
+ end
53
+
54
+ def format(tag, time, record)
55
+ time = record['time'] if record['time'] && @use_import
56
+ [tag, time, record].to_msgpack
57
+ end
58
+
59
+ def write(chunk)
60
+ records = []
61
+ chunk.msgpack_each do |tag, time, record|
62
+ data = {}
63
+ prop = data['properties'] = record.dup
64
+
65
+ # Ignore token in record
66
+ prop.delete('token')
67
+
68
+ if @event_map_tag
69
+ tag.gsub!(/^\./, '') if @use_legacy_prefix_behavior
70
+ data['event'] = tag
71
+ elsif record[@event_key]
72
+ data['event'] = record[@event_key]
73
+ prop.delete(@event_key)
74
+ else
75
+ log.warn("no event, tag: #{tag}, time: #{time.to_s}, record: #{record.to_json}")
76
+ next
77
+ end
78
+
79
+ # Ignore browswer only special event
80
+ next if data['event'].start_with?('mp_')
81
+
82
+ if record[@distinct_id_key]
83
+ data['distinct_id'] = record[@distinct_id_key]
84
+ prop.delete(@distinct_id_key)
85
+ else
86
+ log.warn("no distinct_id, tag: #{tag}, time: #{time.to_s}, record: #{record.to_json}")
87
+ next
88
+ end
89
+
90
+ if !@ip_key.nil? and record[@ip_key]
91
+ prop['ip'] = record[@ip_key]
92
+ prop.delete(@ip_key)
93
+ end
94
+
95
+ prop.select! {|key, _| !key.start_with?('mp_') }
96
+ prop.merge!('time' => time.to_i)
97
+
98
+ records << data
99
+ end
100
+
101
+ send_to_mixpanel(records)
102
+ end
103
+
104
+ def send_to_mixpanel(records)
105
+ log.debug("sending #{records.length} to mixpanel")
106
+
107
+ records.each do |record|
108
+ success = true
109
+
110
+ if @use_import
111
+ success = @tracker.import(@api_key, record['distinct_id'], record['event'], record['properties'])
112
+ else
113
+ success = @tracker.track(record['distinct_id'], record['event'], record['properties'])
114
+ end
115
+
116
+ unless success
117
+ if @discard_event_on_send_error
118
+ msg = "Failed to track event to mixpanel:\n"
119
+ msg += "\tRecord: #{record.to_json}"
120
+ log.info(msg)
121
+ else
122
+ raise MixpanelError.new("Failed to track event to mixpanel")
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'webmock/test_unit'
12
+ WebMock.disable_net_connect!(:allow_localhost => true)
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+ require 'fluent/test'
17
+ unless ENV.has_key?('VERBOSE')
18
+ nulllogger = Object.new
19
+ nulllogger.instance_eval {|obj|
20
+ def method_missing(method, *args)
21
+ # pass
22
+ end
23
+ }
24
+ $log = nulllogger
25
+ end
26
+
27
+ def unused_port
28
+ s = TCPServer.open(0)
29
+ port = s.addr[1]
30
+ s.close
31
+ port
32
+ end
33
+
34
+ require 'fluent/plugin/mixpanel_ruby_error_handler'
35
+ require 'fluent/plugin/out_mixpanel'
36
+ require 'fluent/plugin/in_http_mixpanel'
37
+
38
+ class Test::Unit::TestCase
39
+ end
@@ -0,0 +1,129 @@
1
+ require 'helper'
2
+ require 'net/http'
3
+ require 'base64'
4
+ require 'fluent/test'
5
+ require 'net/http'
6
+
7
+ class HttpMixpanelInputTest < Test::Unit::TestCase
8
+
9
+ class << self
10
+ def startup
11
+ socket_manager_path = ServerEngine::SocketManager::Server.generate_path
12
+ @server = ServerEngine::SocketManager::Server.open(socket_manager_path)
13
+ ENV['SERVERENGINE_SOCKETMANAGER_PATH'] = socket_manager_path.to_s
14
+ end
15
+
16
+ def shutdown
17
+ @server.close
18
+ end
19
+ end
20
+
21
+ def setup
22
+ Fluent::Test.setup
23
+ end
24
+
25
+ PORT = unused_port
26
+ CONFIG = %[
27
+ port #{PORT}
28
+ bind "127.0.0.1"
29
+ body_size_limit 10m
30
+ keepalive_timeout 5
31
+ respond_with_empty_img true
32
+ ]
33
+
34
+ def create_driver(conf=CONFIG)
35
+ Fluent::Test::InputTestDriver.new(Fluent::HttpMixpanelInput).configure(conf, true)
36
+ end
37
+
38
+ def test_configure
39
+ d = create_driver
40
+ assert_equal PORT, d.instance.port
41
+ assert_equal '127.0.0.1', d.instance.bind
42
+ assert_equal 10*1024*1024, d.instance.body_size_limit
43
+ assert_equal 5, d.instance.keepalive_timeout
44
+ assert_equal false, d.instance.add_http_headers
45
+ end
46
+
47
+ def test_time
48
+ d = create_driver
49
+
50
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
51
+ Fluent::Engine.now = time
52
+
53
+ d.expect_emit "mixpanel.tag1", time, {"a"=>1}
54
+ d.expect_emit "mixpanel.tag2", time, {"a"=>2}
55
+
56
+ d.run do
57
+ d.expected_emits.each {|tag, record_time, record|
58
+ res = track("#{tag}", {"json"=>record})
59
+ assert_equal "200", res.code
60
+ assert_equal '1', res.body
61
+ assert_equal 'true', res['access-control-allow-credentials']
62
+ assert_equal 'X-Requested-With', res['access-control-allow-headers']
63
+ assert_equal 'GET, POST, OPTIONS', res['access-control-allow-methods']
64
+ assert_equal 'http://foo.example', res['access-control-allow-origin']
65
+ assert_equal '1728000', res['access-control-max-age']
66
+ assert_equal 'no-cache, no-store', res['cache-control']
67
+ }
68
+ end
69
+ end
70
+
71
+ def test_json
72
+ d = create_driver
73
+
74
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
75
+
76
+ d.expect_emit "mixpanel.tag1", time, {"a"=>1}
77
+ d.expect_emit "mixpanel.tag2", time, {"a"=>2}
78
+
79
+ d.run do
80
+ d.expected_emits.each {|tag, record_time, record|
81
+ res = track("#{tag}", {"json"=>record, "time"=>record_time.to_s})
82
+ assert_equal "200", res.code
83
+ }
84
+ end
85
+
86
+ d.emit_streams.each { |tag, es|
87
+ assert !include_http_header?(es.first[1])
88
+ }
89
+ end
90
+
91
+ def test_json_with_add_http_headers
92
+ d = create_driver(CONFIG + "add_http_headers true")
93
+
94
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
95
+
96
+ records = [["mixpanel.tag1", time, {"a"=>1}], ["mixpanel.tag2", time, {"a"=>2}]]
97
+
98
+ d.run do
99
+ records.each {|tag, record_time, record|
100
+ res = track("#{tag}", {"json"=>record, "time"=>record_time.to_s})
101
+ assert_equal "200", res.code
102
+ }
103
+ end
104
+
105
+ d.emit_streams.each { |tag, es|
106
+ assert include_http_header?(es.first[1])
107
+ }
108
+ end
109
+
110
+ def track(tag, params)
111
+ event = tag.sub(/^mixpanel\.(.+)$/, '\1')
112
+ params['json']['time'] = params['time'] if params['time']
113
+ data = {
114
+ event: event,
115
+ properties: params['json']
116
+ }
117
+ data = CGI.escape(Base64.encode64(data.to_json))
118
+ query = "data=#{data}"
119
+ path = "/track/?#{query}"
120
+
121
+ http = Net::HTTP.new("127.0.0.1", PORT)
122
+ req = Net::HTTP::Get.new(path, { 'origin' => 'http://foo.example' })
123
+ http.request(req)
124
+ end
125
+
126
+ def include_http_header?(record)
127
+ record.keys.find { |header| header.start_with?('HTTP_') }
128
+ end
129
+ end
@@ -0,0 +1,16 @@
1
+ require 'helper'
2
+
3
+ class MixpanelRubyErrorHandlerTest < Test::Unit::TestCase
4
+
5
+ def test_handle
6
+ mixpanel_error = Fluent::MixpanelOutput::MixpanelError.new("Foobar failed")
7
+ @io = StringIO.new
8
+ logger = Logger.new(@io)
9
+ error = Fluent::MixpanelOutputErrorHandler.new(logger)
10
+ error.handle(mixpanel_error)
11
+
12
+ output = @io.string
13
+ expected_output = "MixpanelOutputErrorHandler:\n\tClass: Fluent::MixpanelOutput::MixpanelError\n\tMessage: Foobar failed\n\tBacktrace: \n"
14
+ assert_match expected_output, output
15
+ end
16
+ end
@@ -0,0 +1,371 @@
1
+ require 'helper'
2
+ require 'uri'
3
+ require 'msgpack'
4
+
5
+ class MixpanelOutputTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ Fluent::Test.setup
9
+ @out = []
10
+ end
11
+
12
+ CONFIG = %[
13
+ project_token test_token
14
+ distinct_id_key user_id
15
+ ]
16
+
17
+ IMPORT_CONFIG = CONFIG + %[ api_key test_api_key
18
+ use_import true
19
+ use_legacy_prefix_behavior false
20
+ ]
21
+
22
+ def create_driver(conf = CONFIG)
23
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::MixpanelOutput, 'mixpanel.test').configure(conf)
24
+ end
25
+
26
+ def stub_mixpanel(url="https://api.mixpanel.com/track")
27
+ stub_request(:post, url).with do |req|
28
+ body = URI.decode_www_form(req.body)
29
+ @out << JSON.load(Base64.decode64(body.assoc('data').last))
30
+ end.to_return(status: 200, body: JSON.generate({ status: 1 }))
31
+ end
32
+
33
+ def stub_mixpanel_import
34
+ stub_mixpanel("https://api.mixpanel.com/import")
35
+ end
36
+
37
+ def stub_mixpanel_unavailable(url="https://api.mixpanel.com/track")
38
+ stub_request(:post, url).to_return(status: 503, body: "Service Unavailable")
39
+ end
40
+
41
+ def sample_record
42
+ { user_id: "123", event: "event1", key1: "value1", key2: "value2" }
43
+ end
44
+
45
+ def test_configure
46
+ d = create_driver(CONFIG + "event_key event")
47
+
48
+ assert_equal 'test_token', d.instance.project_token
49
+ assert_equal 'user_id', d.instance.distinct_id_key
50
+ assert_equal 'event', d.instance.event_key
51
+ end
52
+
53
+ def test_configure_with_ip_key
54
+ d = create_driver(CONFIG + "event_key event\n ip_key ip")
55
+
56
+ assert_equal 'test_token', d.instance.project_token
57
+ assert_equal 'user_id', d.instance.distinct_id_key
58
+ assert_equal 'event', d.instance.event_key
59
+ assert_equal 'ip', d.instance.ip_key
60
+ end
61
+
62
+ def test_configure_with_event_map_tag
63
+ d = create_driver(CONFIG + "event_map_tag true")
64
+
65
+ assert_equal 'test_token', d.instance.project_token
66
+ assert_equal 'user_id', d.instance.distinct_id_key
67
+ assert_equal nil, d.instance.event_key
68
+ assert_equal true.to_s, d.instance.event_map_tag
69
+ end
70
+
71
+ def test_write
72
+ stub_mixpanel
73
+ d = create_driver(CONFIG + "event_key event")
74
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
75
+ d.emit(sample_record, time)
76
+ d.run
77
+
78
+ assert_equal "test_token", @out[0]['properties']['token']
79
+ assert_equal "123", @out[0]['properties']['distinct_id']
80
+ assert_equal "event1", @out[0]['event']
81
+ assert_equal time, @out[0]['properties']['time']
82
+ assert_equal "value1", @out[0]['properties']['key1']
83
+ assert_equal "value2", @out[0]['properties']['key2']
84
+ end
85
+
86
+ def test_write_setting_time_via_export
87
+ stub_mixpanel_import
88
+ d = create_driver(CONFIG + "use_import true\nevent_key event")
89
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
90
+ d.emit(sample_record.merge!('time' => 1435707767), time)
91
+ d.run
92
+
93
+ assert_equal "test_token", @out[0]['properties']['token']
94
+ assert_equal "123", @out[0]['properties']['distinct_id']
95
+ assert_equal "event1", @out[0]['event']
96
+ assert_equal 1435707767, @out[0]['properties']['time']
97
+ assert_equal "value1", @out[0]['properties']['key1']
98
+ assert_equal "value2", @out[0]['properties']['key2']
99
+ end
100
+
101
+ def test_write_multi_request
102
+ stub_mixpanel_import
103
+ d = create_driver(IMPORT_CONFIG + "event_key event")
104
+ time1 = Time.new('2014-01-01T01:23:45+00:00').to_i
105
+ time2 = Time.new('2014-01-02T01:23:45+00:00').to_i
106
+
107
+ d.emit(sample_record, time1)
108
+ d.emit(sample_record.merge(key3: "value3"), time2)
109
+ d.run
110
+
111
+ assert_equal "123", @out[0]['properties']['distinct_id']
112
+ assert_equal "event1", @out[0]['event']
113
+ assert_equal time1, @out[0]['properties']['time']
114
+ assert_equal "value1", @out[0]['properties']['key1']
115
+ assert_equal "value2", @out[0]['properties']['key2']
116
+
117
+ assert_equal "123", @out[1]['properties']['distinct_id']
118
+ assert_equal "event1", @out[1]['event']
119
+ assert_equal time2, @out[1]['properties']['time']
120
+ assert_equal "value1", @out[1]['properties']['key1']
121
+ assert_equal "value2", @out[1]['properties']['key2']
122
+ assert_equal "value2", @out[1]['properties']['key2']
123
+ end
124
+
125
+ def test_write_with_ip_key
126
+ stub_mixpanel
127
+ d = create_driver(CONFIG + "event_key event\n ip_key ip_address")
128
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
129
+ d.emit(sample_record.merge('ip_address' => '192.168.0.2'), time)
130
+ d.run
131
+
132
+ assert_equal "123", @out[0]['properties']['distinct_id']
133
+ assert_equal "event1", @out[0]['event']
134
+ assert_equal time, @out[0]['properties']['time']
135
+ assert_equal "192.168.0.2", @out[0]['properties']['ip']
136
+ assert_equal "value1", @out[0]['properties']['key1']
137
+ assert_equal "value2", @out[0]['properties']['key2']
138
+ end
139
+
140
+ def test_write_with_no_tag_manipulation
141
+ stub_mixpanel
142
+ d = create_driver(CONFIG + "event_map_tag true")
143
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
144
+ d.emit(sample_record, time)
145
+ d.run
146
+
147
+ assert_equal "123", @out[0]['properties']['distinct_id']
148
+ assert_equal "mixpanel.test", @out[0]['event']
149
+ assert_equal time, @out[0]['properties']['time']
150
+ assert_equal "value1", @out[0]['properties']['key1']
151
+ assert_equal "value2", @out[0]['properties']['key2']
152
+ end
153
+
154
+ def test_write_with_event_map_tag_removing_prefix
155
+ stub_mixpanel
156
+ d = create_driver(CONFIG + "remove_tag_prefix mixpanel.\n event_map_tag true")
157
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
158
+ d.emit(sample_record, time)
159
+ d.run
160
+
161
+ assert_equal "123", @out[0]['properties']['distinct_id']
162
+ assert_equal "test", @out[0]['event']
163
+ assert_equal time, @out[0]['properties']['time']
164
+ assert_equal "value1", @out[0]['properties']['key1']
165
+ assert_equal "value2", @out[0]['properties']['key2']
166
+ end
167
+
168
+ def test_write_with_event_map_tag_removing_prefix_LEGACY
169
+ stub_mixpanel
170
+ d = create_driver(CONFIG + "remove_tag_prefix mixpanel\n event_map_tag true\n use_legacy_prefix_behavior true")
171
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
172
+ d.emit(sample_record, time)
173
+ d.run
174
+
175
+ assert_equal "123", @out[0]['properties']['distinct_id']
176
+ assert_equal "test", @out[0]['event']
177
+ assert_equal time, @out[0]['properties']['time']
178
+ assert_equal "value1", @out[0]['properties']['key1']
179
+ assert_equal "value2", @out[0]['properties']['key2']
180
+ end
181
+
182
+ def test_write_with_event_map_tag_removing_prefix_LEGACY_with_dot
183
+ stub_mixpanel
184
+ d = create_driver(CONFIG + "remove_tag_prefix mixpanel.\n event_map_tag true\n use_legacy_prefix_behavior true")
185
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
186
+ d.emit(sample_record, time)
187
+ d.run
188
+
189
+ assert_equal "123", @out[0]['properties']['distinct_id']
190
+ assert_equal "test", @out[0]['event']
191
+ assert_equal time, @out[0]['properties']['time']
192
+ assert_equal "value1", @out[0]['properties']['key1']
193
+ assert_equal "value2", @out[0]['properties']['key2']
194
+ end
195
+
196
+ def test_write_with_event_map_tag_removing_suffix
197
+ stub_mixpanel
198
+ d = create_driver(CONFIG + "remove_tag_suffix .test\n event_map_tag true")
199
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
200
+ d.emit(sample_record, time)
201
+ d.run
202
+
203
+ assert_equal "123", @out[0]['properties']['distinct_id']
204
+ assert_equal "mixpanel", @out[0]['event']
205
+ assert_equal time, @out[0]['properties']['time']
206
+ assert_equal "value1", @out[0]['properties']['key1']
207
+ assert_equal "value2", @out[0]['properties']['key2']
208
+ end
209
+
210
+ def test_write_with_event_map_tag_adding_prefix
211
+ stub_mixpanel
212
+ d = create_driver(CONFIG + "add_tag_prefix foo.\n event_map_tag true")
213
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
214
+ d.emit(sample_record, time)
215
+ d.run
216
+
217
+ assert_equal "123", @out[0]['properties']['distinct_id']
218
+ assert_equal "foo.mixpanel.test", @out[0]['event']
219
+ assert_equal time, @out[0]['properties']['time']
220
+ assert_equal "value1", @out[0]['properties']['key1']
221
+ assert_equal "value2", @out[0]['properties']['key2']
222
+ end
223
+
224
+ def test_write_with_event_map_tag_adding_suffix
225
+ stub_mixpanel
226
+ d = create_driver(CONFIG + "add_tag_suffix .foo\n event_map_tag true")
227
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
228
+ d.emit(sample_record, time)
229
+ d.run
230
+
231
+ assert_equal "123", @out[0]['properties']['distinct_id']
232
+ assert_equal "mixpanel.test.foo", @out[0]['event']
233
+ assert_equal time, @out[0]['properties']['time']
234
+ assert_equal "value1", @out[0]['properties']['key1']
235
+ assert_equal "value2", @out[0]['properties']['key2']
236
+ end
237
+
238
+ def test_write_ignore_special_event
239
+ stub_mixpanel
240
+ d = create_driver(CONFIG + "event_key event")
241
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
242
+ d.emit({ user_id: '123', event: 'mp_page_view' }, time)
243
+ d.run
244
+
245
+ assert_equal 0, @out.length
246
+ end
247
+
248
+ def test_write_ignore_special_property
249
+ stub_mixpanel
250
+ d = create_driver(CONFIG + "event_key event")
251
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
252
+ d.emit(sample_record.merge('mp_event' => '3'), time)
253
+ d.run
254
+
255
+ assert_equal "test_token", @out[0]['properties']['token']
256
+ assert_equal "123", @out[0]['properties']['distinct_id']
257
+ assert_equal "event1", @out[0]['event']
258
+ assert_equal time, @out[0]['properties']['time']
259
+ assert_equal "value1", @out[0]['properties']['key1']
260
+ assert_equal "value2", @out[0]['properties']['key2']
261
+ assert_equal false, @out[0]['properties'].key?('mp_event')
262
+ end
263
+
264
+ def test_write_delete_supried_token
265
+ stub_mixpanel
266
+ d = create_driver(CONFIG + "event_key event")
267
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
268
+ d.emit(sample_record.merge('token' => '123'), time)
269
+ d.run
270
+
271
+ assert_equal "test_token", @out[0]['properties']['token']
272
+ assert_equal "123", @out[0]['properties']['distinct_id']
273
+ assert_equal "event1", @out[0]['event']
274
+ assert_equal time, @out[0]['properties']['time']
275
+ assert_equal "value1", @out[0]['properties']['key1']
276
+ assert_equal "value2", @out[0]['properties']['key2']
277
+ assert_equal false, @out[0]['properties'].key?('mp_event')
278
+ end
279
+
280
+ def test_request_error
281
+ stub_mixpanel_unavailable
282
+ d = create_driver(CONFIG + "event_key event")
283
+ d.emit(sample_record)
284
+ assert_raise(Fluent::MixpanelOutput::MixpanelError) {
285
+ d.run
286
+ }
287
+ end
288
+
289
+ def test_multiple_records_1_missing_event
290
+ stub_mixpanel
291
+ d = create_driver(CONFIG + "event_key event")
292
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
293
+ d.emit(sample_record, time)
294
+
295
+ broken_record = sample_record.dup.delete(:event)
296
+ d.emit(broken_record, time)
297
+
298
+ d.run
299
+
300
+ assert_equal 1, @out.length
301
+
302
+ assert_equal "test_token", @out[0]['properties']['token']
303
+ assert_equal "123", @out[0]['properties']['distinct_id']
304
+ assert_equal "event1", @out[0]['event']
305
+ assert_equal time, @out[0]['properties']['time']
306
+ assert_equal "value1", @out[0]['properties']['key1']
307
+ assert_equal "value2", @out[0]['properties']['key2']
308
+
309
+ end
310
+
311
+ def test_multiple_records_1_missing_distinct_id
312
+ stub_mixpanel
313
+ d = create_driver(CONFIG + "event_key event")
314
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
315
+ d.emit(sample_record, time)
316
+
317
+ broken_record = sample_record.dup.delete(:user_id)
318
+ d.emit(broken_record, time)
319
+
320
+ d.run
321
+
322
+ assert_equal 1, @out.length
323
+
324
+ assert_equal "test_token", @out[0]['properties']['token']
325
+ assert_equal "123", @out[0]['properties']['distinct_id']
326
+ assert_equal "event1", @out[0]['event']
327
+ assert_equal time, @out[0]['properties']['time']
328
+ assert_equal "value1", @out[0]['properties']['key1']
329
+ assert_equal "value2", @out[0]['properties']['key2']
330
+
331
+ end
332
+
333
+ def test_multiple_records_1_having_mp
334
+ stub_mixpanel
335
+ d = create_driver(CONFIG + "event_key event")
336
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
337
+ d.emit(sample_record, time)
338
+
339
+ broken_record = sample_record.dup.merge({ event: 'mp_foo'})
340
+ d.emit(broken_record, time)
341
+
342
+ d.run
343
+
344
+ assert_equal 1, @out.length
345
+
346
+ assert_equal "test_token", @out[0]['properties']['token']
347
+ assert_equal "123", @out[0]['properties']['distinct_id']
348
+ assert_equal "event1", @out[0]['event']
349
+ assert_equal time, @out[0]['properties']['time']
350
+ assert_equal "value1", @out[0]['properties']['key1']
351
+ assert_equal "value2", @out[0]['properties']['key2']
352
+
353
+ end
354
+
355
+ def test_request_error_discard
356
+ stub_mixpanel_unavailable
357
+ d = create_driver(CONFIG + "event_key event\ndiscard_event_on_send_error true")
358
+ time = Time.new('2014-01-01T01:23:45+00:00').to_i
359
+ d.emit(sample_record, time)
360
+ d.run
361
+
362
+ logs = d.instance.log.logs
363
+
364
+ assert_match "MixpanelOutputErrorHandler:", logs[0]
365
+ assert_match "Class: Mixpanel::ServerError", logs[0]
366
+ assert_match "Message: Could not write to Mixpanel, server responded with 503 returning: 'Service Unavailable", logs[0]
367
+ assert_match "Backtrace", logs[0]
368
+ assert_match "Failed to track event to mixpanel", logs[1]
369
+ assert_match 'Record: {"properties":{"key1":"value1","key2":"value2","time":' + time.to_s + '},"event":"event1","distinct_id":"123"}', logs[1]
370
+ end
371
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-mixpanel-enchanced
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.10
5
+ platform: ruby
6
+ authors:
7
+ - zhron4x
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.55
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.55
27
+ - !ruby/object:Gem::Dependency
28
+ name: mixpanel-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
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
+ description: Fluentd plugin to input/output event track data to mixpanel
98
+ email:
99
+ - zhr0n4x@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".travis.yml"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - example/http_server.sh
111
+ - example/in_http_mixpanel.conf
112
+ - example/index.html
113
+ - fluent-plugin-mixpanel-enchanced.gemspec
114
+ - lib/fluent/plugin/in_http_mixpanel.rb
115
+ - lib/fluent/plugin/mixpanel_ruby_error_handler.rb
116
+ - lib/fluent/plugin/out_mixpanel.rb
117
+ - test/helper.rb
118
+ - test/plugin/test_in_http_mixpanel.rb
119
+ - test/plugin/test_mixpanel_ruby_error_handler.rb
120
+ - test/plugin/test_out_mixpanel.rb
121
+ homepage: https://github.com/zhron4x/fluent-plugin-mixpanel-enchanced
122
+ licenses:
123
+ - Apache License, Version 2.0
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.6.8
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Fluentd plugin to input/output event track data to mixpanel
145
+ test_files:
146
+ - test/helper.rb
147
+ - test/plugin/test_in_http_mixpanel.rb
148
+ - test/plugin/test_mixpanel_ruby_error_handler.rb
149
+ - test/plugin/test_out_mixpanel.rb