fluent-plugin-cloudwatch-logs-yajl 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1a9ff9da35695288dfdbeca3ba8ed98a7a7ff2cd
4
+ data.tar.gz: 0d2dc63be5193df07e3dcbf91c229dbbf8797428
5
+ SHA512:
6
+ metadata.gz: 4e48ea1183b4d9bbc2e491cce9e6ef9ccf5133099851182c637cb69573d033bae47fa06daded92b196fa67867a1289fa18b103c674097ffc900c32b63e2be22d
7
+ data.tar.gz: 570be1022decbab4f8f9ea124e149060f78aeca0f7caf1f7e546c1a7ecb77bd31bd31914ac824dbcee565a5a0c6e6e421f52f698d20fb085f0add272dd6ad4a2
@@ -0,0 +1,22 @@
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
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-cloudwatch-logs.gemspec
4
+ gem 'fluentd', '~> 0.12.0'
5
+ gemspec
@@ -0,0 +1,22 @@
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.
@@ -0,0 +1,266 @@
1
+ # fluent-plugin-cloudwatch-logs
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/fluent-plugin-cloudwatch-logs.svg)](http://badge.fury.io/rb/fluent-plugin-cloudwatch-logs)
4
+
5
+ [CloudWatch Logs](http://aws.amazon.com/blogs/aws/cloudwatch-log-service/) Plugin for Fluentd
6
+
7
+ ## Installation
8
+
9
+ $ gem install fluent-plugin-cloudwatch-logs
10
+
11
+ ## Preparation
12
+
13
+ Create IAM user with a policy like the following:
14
+
15
+ ```json
16
+ {
17
+ "Version": "2012-10-17",
18
+ "Statement": [
19
+ {
20
+ "Effect": "Allow",
21
+ "Action": [
22
+ "logs:*",
23
+ "s3:GetObject"
24
+ ],
25
+ "Resource": [
26
+ "arn:aws:logs:us-east-1:*:*",
27
+ "arn:aws:s3:::*"
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ Set region and credentials:
35
+
36
+ ```
37
+ $ export AWS_REGION=us-east-1
38
+ $ export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
39
+ $ export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_ACCESS_KEY"
40
+ ```
41
+
42
+ ## Example
43
+
44
+ Start fluentd:
45
+
46
+ ```
47
+ $ fluentd -c example/fluentd.conf
48
+ ```
49
+
50
+ Send sample log to CloudWatch Logs:
51
+
52
+ ```
53
+ $ echo '{"hello":"world"}' | fluent-cat test.cloudwatch_logs.out
54
+ ```
55
+
56
+ Fetch sample log from CloudWatch Logs:
57
+
58
+ ```
59
+ # stdout
60
+ 2014-07-17 00:28:02 +0900 test.cloudwatch_logs.in: {"hello":"world"}
61
+ ```
62
+
63
+ ## Configuration
64
+ ### out_cloudwatch_logs
65
+
66
+ ```
67
+ <match tag>
68
+ type cloudwatch_logs
69
+ log_group_name log-group-name
70
+ log_stream_name log-stream-name
71
+ auto_create_stream true
72
+ #message_keys key1,key2,key3,...
73
+ #max_message_length 32768
74
+ #use_tag_as_group false
75
+ #use_tag_as_stream false
76
+ #include_time_key true
77
+ #localtime true
78
+ #log_group_name_key group_name_key
79
+ #log_stream_name_key stream_name_key
80
+ #remove_log_group_name_key true
81
+ #remove_log_stream_name_key true
82
+ #put_log_events_retry_wait 1s
83
+ #put_log_events_retry_limit 17
84
+ #put_log_events_disable_retry_limit false
85
+ </match>
86
+ ```
87
+
88
+ * `log_group_name`: name of log group to store logs
89
+ * `log_stream_name`: name of log stream to store logs
90
+ * `auto_create_stream`: to create log group and stream automatically
91
+ * `message_keys`: keys to send messages as events
92
+ * `max_message_length`: maximum length of the message
93
+ * `max_events_per_batch`: maximum number of events to send at once (default 10000)
94
+ * `use_tag_as_group`: to use tag as a group name
95
+ * `use_tag_as_stream`: to use tag as a stream name
96
+ * `include_time_key`: include time key as part of the log entry (defaults to UTC)
97
+ * `localtime`: use localtime timezone for `include_time_key` output (overrides UTC default)
98
+ * `log_group_name_key`: use specified field of records as log group name
99
+ * `log_stream_name_key`: use specified field of records as log stream name
100
+ * `remove_log_group_name_key`: remove field specified by `log_group_name_key`
101
+ * `remove_log_stream_name_key`: remove field specified by `log_stream_name_key`
102
+ * `put_log_events_retry_wait`: time before retrying PutLogEvents (retry interval increases exponentially like `put_log_events_retry_wait * (2 ^ retry_count)`)
103
+ * `put_log_events_retry_limit`: maximum count of retry (if exceeding this, the events will be discarded)
104
+ * `put_log_events_disable_retry_limit`: if true, `put_log_events_retry_limit` will be ignored
105
+
106
+ ### in_cloudwatch_logs
107
+
108
+ ```
109
+ <source>
110
+ type cloudwatch_logs
111
+ tag cloudwatch.in
112
+ log_group_name group
113
+ log_stream_name stream
114
+ #use_log_stream_name_prefix true
115
+ state_file /var/lib/fluent/group_stream.in.state
116
+ </source>
117
+ ```
118
+
119
+ * `tag`: fluentd tag
120
+ * `log_group_name`: name of log group to fetch logs
121
+ * `log_stream_name`: name of log stream to fetch logs
122
+ * `use_log_stream_name_prefix`: to use `log_stream_name` as log stream name prefix (default false)
123
+ * `state_file`: file to store current state (e.g. next\_forward\_token)
124
+ * `aws_use_sts`: use [AssumeRoleCredentials](http://docs.aws.amazon.com/sdkforruby/api/Aws/AssumeRoleCredentials.html) to authenticate, rather than the [default credential hierarchy](http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudWatchLogs/Client.html#initialize-instance_method). See 'Cross-Account Operation' below for more detail.
125
+ * `aws_sts_role_arn`: the role ARN to assume when using cross-account sts authentication
126
+ * `aws_sts_session_name`: the session name to use with sts authentication (default: `fluentd`)
127
+
128
+ This plugin uses [fluent-mixin-config-placeholders](https://github.com/tagomoris/fluent-mixin-config-placeholders) and you can use addtional variables such as %{hostname}, %{uuid}, etc. These variables are useful to put hostname in `log_stream_name`.
129
+
130
+ ## Test
131
+
132
+ Set credentials:
133
+
134
+ ```
135
+ $ export AWS_REGION=us-east-1
136
+ $ export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
137
+ $ export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
138
+ ```
139
+
140
+ Run tests:
141
+
142
+ ```
143
+ $ rake test
144
+ ```
145
+
146
+ Or, If you do not want to use IAM roll or ENV(this is just like writing to configuration file) :
147
+
148
+ ```
149
+ $ rake aws_key_id=YOUR_ACCESS_KEY aws_sec_key=YOUR_SECRET_KEY region=us-east-1 test
150
+ ```
151
+
152
+ ## Caution
153
+
154
+ - If an event message exceeds API limit (256KB), the event will be discarded.
155
+
156
+ ## Cross-Account Operation
157
+ In order to have an instance of this plugin running in one AWS account to fetch logs from another account cross-account IAM authentication is required. Whilst this can be accomplished by configuring specific instances of the plugin manually with credentials for the source account in question this is not desirable for a number of reasons.
158
+
159
+ In this case IAM can be used to allow the fluentd instance in one account ("A") to ingest Cloudwatch logs from another ("B") via the following mechanic:
160
+
161
+ * plugin instance running in account "A" has an IAM instance role assigned to the underlying EC2 instance
162
+ * The IAM instance role and associated policies permit the EC2 instance to assume a role in another account
163
+ * An IAM role in account "B" and associated policies allow read access to the Cloudwatch Logs service, as appropriate.
164
+
165
+ ### IAM Detail: Consuming Account "A"
166
+
167
+ * Create an IAM role `cloudwatch`
168
+ * Attach a policy to allow the role holder to assume another role (where `ACCOUNT-B` is substituted for the appropriate account number):
169
+
170
+ ```
171
+ {
172
+ "Version": "2012-10-17",
173
+ "Statement": [
174
+ {
175
+ "Effect": "Allow",
176
+ "Action": [
177
+ "sts:*"
178
+ ],
179
+ "Resource": [
180
+ "arn:aws:iam::ACCOUNT-B:role/fluentd"
181
+ ]
182
+ }
183
+ ]
184
+ }
185
+ ```
186
+
187
+ * Ensure the EC2 instance on which this plugin is executing as role `cloudwatch` as its assigned IAM instance role.
188
+
189
+ ### IAM Detail: Log Source Account "B"
190
+
191
+ * Create an IAM role `fluentd`
192
+ * Ensure the `fluentd` role as account "A" as a trusted entity:
193
+
194
+ ```
195
+ {
196
+ "Version": "2012-10-17",
197
+ "Statement": [
198
+ {
199
+ "Effect": "Allow",
200
+ "Principal": {
201
+ "AWS": "arn:aws:iam::ACCOUNT-A:root"
202
+ },
203
+ "Action": "sts:AssumeRole"
204
+ }
205
+ ]
206
+ }
207
+ ```
208
+
209
+ * Attach a policy:
210
+
211
+ ```
212
+ {
213
+ "Version": "2012-10-17",
214
+ "Statement": [
215
+ {
216
+ "Effect": "Allow",
217
+ "Action": [
218
+ "logs:DescribeDestinations",
219
+ "logs:DescribeExportTasks",
220
+ "logs:DescribeLogGroups",
221
+ "logs:DescribeLogStreams",
222
+ "logs:DescribeMetricFilters",
223
+ "logs:DescribeSubscriptionFilters",
224
+ "logs:FilterLogEvents",
225
+ "logs:GetLogEvents"
226
+ ],
227
+ "Resource": [
228
+ "arn:aws:logs:eu-west-1:ACCOUNT-B:log-group:LOG_GROUP_NAME_FOR_CONSUMPTION:*"
229
+ ]
230
+ }
231
+ ]
232
+ }
233
+ ```
234
+
235
+ ### Configuring the plugin for STS authentication
236
+ ```
237
+ <source>
238
+ type cloudwatch_logs
239
+ region us-east-1 # You must supply a region
240
+ aws_use_sts true
241
+ aws_sts_role_arn arn:aws:iam::ACCOUNT-B:role/fluentd
242
+ log_group_name LOG_GROUP_NAME_FOR_CONSUMPTION
243
+ log_stream_name SOME_PREFIX
244
+ use_log_stream_name_prefix true
245
+ state_file /path/to/state_file
246
+ format /(?<message>.+)/
247
+ </source>
248
+ ```
249
+
250
+ ## TODO
251
+
252
+ * out_cloudwatch_logs
253
+ * if the data is too big for API, split into multiple requests
254
+ * format
255
+ * check data size
256
+ * in_cloudwatch_logs
257
+ * format
258
+ * fallback to start_time because next_token expires after 24 hours
259
+
260
+ ## Contributing
261
+
262
+ 1. Fork it ( https://github.com/[my-github-username]/fluent-plugin-cloudwatch-logs/fork )
263
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
264
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
265
+ 4. Push to the branch (`git push origin my-new-feature`)
266
+ 5. Create a new Pull Request
@@ -0,0 +1,9 @@
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
+
@@ -0,0 +1,23 @@
1
+ <source>
2
+ type forward
3
+ </source>
4
+
5
+ <source>
6
+ type cloudwatch_logs
7
+ tag test.cloudwatch_logs.in
8
+ log_group_name fluent-plugin-cloudwatch-example
9
+ log_stream_name fluent-plugin-cloudwatch-example
10
+ state_file /tmp/fluent-plugin-cloudwatch-example.state
11
+ </source>
12
+
13
+ <match test.cloudwatch_logs.out>
14
+ type cloudwatch_logs
15
+ log_group_name fluent-plugin-cloudwatch-example
16
+ log_stream_name fluent-plugin-cloudwatch-example
17
+ auto_create_stream true
18
+ </match>
19
+
20
+ <match test.cloudwatch_logs.in>
21
+ type stdout
22
+ </match>
23
+
@@ -0,0 +1,29 @@
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/cloudwatch/logs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fluent-plugin-cloudwatch-logs-yajl"
8
+ spec.version = Fluent::Plugin::Cloudwatch::Logs::VERSION
9
+ spec.authors = ["Todd Scott"]
10
+ spec.email = ["tscott1213@gmail.com"]
11
+ spec.summary = %q{CloudWatch Logs Plugin for Fluentd}
12
+ spec.homepage = "https://github.com/tscott1213/fluent-plugin-cloudwatch-logs"
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 'yajl-ruby', '~> 1.0'
22
+ spec.add_dependency 'aws-sdk-cloudwatchlogs', '~> 1.0'
23
+ spec.add_dependency 'fluent-mixin-config-placeholders', '>= 0.2.0'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.6"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "test-unit"
28
+ spec.add_development_dependency "mocha"
29
+ end
@@ -0,0 +1,11 @@
1
+ require "fluent/plugin/cloudwatch/logs/version"
2
+
3
+ module Fluent
4
+ module Plugin
5
+ module Cloudwatch
6
+ module Logs
7
+ # Your code goes here...
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Fluent
2
+ module Plugin
3
+ module Cloudwatch
4
+ module Logs
5
+ VERSION = "0.4.5"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,164 @@
1
+ require 'fluent/input'
2
+ require 'fluent/parser'
3
+
4
+ module Fluent
5
+ require 'fluent/mixin/config_placeholders'
6
+
7
+ class CloudwatchLogsInput < Input
8
+ Plugin.register_input('cloudwatch_logs', self)
9
+
10
+ include Fluent::Mixin::ConfigPlaceholders
11
+
12
+ # Define `router` method of v0.12 to support v0.10.57 or earlier
13
+ unless method_defined?(:router)
14
+ define_method("router") { Engine }
15
+ end
16
+
17
+ config_param :aws_key_id, :string, :default => nil, :secret => true
18
+ config_param :aws_sec_key, :string, :default => nil, :secret => true
19
+ config_param :aws_use_sts, :bool, default: false
20
+ config_param :aws_sts_role_arn, :string, default: nil
21
+ config_param :aws_sts_session_name, :string, default: 'fluentd'
22
+ config_param :region, :string, :default => nil
23
+ config_param :tag, :string
24
+ config_param :log_group_name, :string
25
+ config_param :log_stream_name, :string
26
+ config_param :use_log_stream_name_prefix, :bool, default: false
27
+ config_param :state_file, :string
28
+ config_param :fetch_interval, :time, default: 60
29
+ config_param :http_proxy, :string, default: nil
30
+
31
+ def initialize
32
+ super
33
+
34
+ require 'aws-sdk-cloudwatchlogs'
35
+ end
36
+
37
+ def placeholders
38
+ [:percent]
39
+ end
40
+
41
+ def configure(conf)
42
+ super
43
+ configure_parser(conf)
44
+ end
45
+
46
+ def start
47
+ options = {}
48
+ options[:region] = @region if @region
49
+ options[:http_proxy] = @http_proxy if @http_proxy
50
+
51
+ if @aws_use_sts
52
+ Aws.config[:region] = options[:region]
53
+ options[:credentials] = Aws::AssumeRoleCredentials.new(
54
+ role_arn: @aws_sts_role_arn,
55
+ role_session_name: @aws_sts_session_name
56
+ )
57
+ else
58
+ options[:credentials] = Aws::Credentials.new(@aws_key_id, @aws_sec_key) if @aws_key_id && @aws_sec_key
59
+ end
60
+
61
+ @logs = Aws::CloudWatchLogs::Client.new(options)
62
+
63
+ @finished = false
64
+ @thread = Thread.new(&method(:run))
65
+ end
66
+
67
+ def shutdown
68
+ @finished = true
69
+ @thread.join
70
+ end
71
+
72
+ private
73
+ def configure_parser(conf)
74
+ if conf['format']
75
+ @parser = Fluent::TextParser.new
76
+ @parser.configure(conf)
77
+ end
78
+ end
79
+
80
+ def state_file_for(log_stream_name)
81
+ return "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}" if log_stream_name
82
+ return @state_file
83
+ end
84
+
85
+ def next_token(log_stream_name)
86
+ return nil unless File.exist?(state_file_for(log_stream_name))
87
+ File.read(state_file_for(log_stream_name)).chomp
88
+ end
89
+
90
+ def store_next_token(token, log_stream_name = nil)
91
+ open(state_file_for(log_stream_name), 'w') do |f|
92
+ f.write token
93
+ end
94
+ end
95
+
96
+ def run
97
+ @next_fetch_time = Time.now
98
+
99
+ until @finished
100
+ if Time.now > @next_fetch_time
101
+ @next_fetch_time += @fetch_interval
102
+
103
+ if @use_log_stream_name_prefix
104
+ log_streams = describe_log_streams
105
+ log_streams.each do |log_stream|
106
+ log_stream_name = log_stream.log_stream_name
107
+ events = get_events(log_stream_name)
108
+ events.each do |event|
109
+ emit(log_stream_name, event)
110
+ end
111
+ end
112
+ else
113
+ events = get_events(@log_stream_name)
114
+ events.each do |event|
115
+ emit(log_stream_name, event)
116
+ end
117
+ end
118
+ end
119
+ sleep 1
120
+ end
121
+ end
122
+
123
+ def emit(stream, event)
124
+ if @parser
125
+ record = @parser.parse(event.message)
126
+ router.emit(@tag, record[0], record[1])
127
+ else
128
+ time = (event.timestamp / 1000).floor
129
+ record = Yajl.load(event.message)
130
+ router.emit(@tag, time, record)
131
+ end
132
+ end
133
+
134
+ def get_events(log_stream_name)
135
+ request = {
136
+ log_group_name: @log_group_name,
137
+ log_stream_name: log_stream_name
138
+ }
139
+ request[:next_token] = next_token(log_stream_name) if next_token(log_stream_name)
140
+ response = @logs.get_log_events(request)
141
+ store_next_token(response.next_forward_token, log_stream_name)
142
+
143
+ response.events
144
+ end
145
+
146
+ def describe_log_streams(log_streams = nil, next_token = nil)
147
+ request = {
148
+ log_group_name: @log_group_name
149
+ }
150
+ request[:next_token] = next_token if next_token
151
+ request[:log_stream_name_prefix] = @log_stream_name
152
+ response = @logs.describe_log_streams(request)
153
+ if log_streams
154
+ log_streams.concat(response.log_streams)
155
+ else
156
+ log_streams = response.log_streams
157
+ end
158
+ if response.next_token
159
+ log_streams = describe_log_streams(log_streams, response.next_token)
160
+ end
161
+ log_streams
162
+ end
163
+ end
164
+ end