fluent-plugin-datadog-log 0.1.0.rc1 → 0.1.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/fluent-plugin-datadog-log.gemspec +1 -1
- data/fluent-plugin-datadog-log.gemspec~ +1 -1
- data/lib/fluent/plugin/out_datadog_log.rb +45 -14
- data/lib/fluent/plugin/out_datadog_log.rb~ +549 -0
- data/pkg/fluent-plugin-datadog-log-0.1.0.rc1.gem +0 -0
- data/pkg/fluent-plugin-datadog-log-0.1.0.rc2.gem +0 -0
- data/test/plugin/test_out_datadog_log.rb +72 -1
- data/test/plugin/test_out_datadog_log.rb~ +206 -0
- metadata +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ec628db3ffd3258f75a1ec766c763f3d4ad70ea
|
4
|
+
data.tar.gz: 9dc3f96c5ff194ab5cdb17e3e7ed1ac7ccfcecfd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a97cd2159cb172acd76cb1ed9141176cac53758dd2d384b6bcd7c77dacf0f45f37e81edc932b092e2c86847b65c3a552f8a2756057ae456364aacfafc3b1f6a
|
7
|
+
data.tar.gz: e26b6ef4808d0d76fc56f0d63b545f7df3497658ed9353fa7aa1cdb00d20f834227a825bb7e3c45f5dfe127a18bad7add8d55b258cca983c103f70a9fd092329
|
data/Gemfile.lock
CHANGED
@@ -8,7 +8,7 @@ eos
|
|
8
8
|
gem.homepage = \
|
9
9
|
'https://github.com/mumoshu/fluent-plugin-datadog-log'
|
10
10
|
gem.license = 'Apache-2.0'
|
11
|
-
gem.version = '0.1.0.
|
11
|
+
gem.version = '0.1.0.rc2'
|
12
12
|
gem.authors = ['Yusuke KUOKA']
|
13
13
|
gem.email = ['ykuoka@gmail.com']
|
14
14
|
gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
|
@@ -8,7 +8,7 @@ eos
|
|
8
8
|
gem.homepage = \
|
9
9
|
'https://github.com/mumoshu/fluent-plugin-datadog-log'
|
10
10
|
gem.license = 'Apache-2.0'
|
11
|
-
gem.version = '0.1.0'
|
11
|
+
gem.version = '0.1.0.rc1'
|
12
12
|
gem.authors = ['Yusuke KUOKA']
|
13
13
|
gem.email = ['ykuoka@gmail.com']
|
14
14
|
gem.required_ruby_version = Gem::Requirement.new('>= 2.0')
|
@@ -128,9 +128,9 @@ module Fluent::Plugin
|
|
128
128
|
super
|
129
129
|
|
130
130
|
if @api_key.size == 0
|
131
|
-
@api_key = ENV['
|
131
|
+
@api_key = ENV['DD_API_KEY']
|
132
132
|
if @api_key == '' || @api_key.nil?
|
133
|
-
error_message = 'Unable to obtain api_key from
|
133
|
+
error_message = 'Unable to obtain api_key from DD_API_KEY'
|
134
134
|
fail Fluent::ConfigError, error_message
|
135
135
|
end
|
136
136
|
end
|
@@ -249,10 +249,26 @@ module Fluent::Plugin
|
|
249
249
|
tags << "#{tag_key}=#{kube[json_key]}" if kube.key? json_key
|
250
250
|
end
|
251
251
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
252
|
+
kube_labels = kube['labels']
|
253
|
+
unless kube_labels.nil?
|
254
|
+
kube_labels.each do |k, v|
|
255
|
+
k2 = k.dup
|
256
|
+
k2.gsub!(/[\,\.]/, '_')
|
257
|
+
k2.gsub!(%r{/}, '-')
|
258
|
+
tags << "kube_#{k2}=#{v}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
if kube.key? 'annotations'
|
263
|
+
annotations = kube['annotations']
|
264
|
+
created_by_str = annotations['kubernetes.io/created-by']
|
265
|
+
unless created_by_str.nil?
|
266
|
+
created_by = JSON.parse(created_by_str)
|
267
|
+
ref = created_by['reference'] unless created_by.nil?
|
268
|
+
kind = ref['kind'] unless ref.nil?
|
269
|
+
name = ref['name'] unless ref.nil?
|
270
|
+
kind = kind.downcase unless kind.nil?
|
271
|
+
tags << "kube_#{kind}=#{name}" if !kind.nil? && !name.nil?
|
256
272
|
end
|
257
273
|
end
|
258
274
|
|
@@ -264,6 +280,14 @@ module Fluent::Plugin
|
|
264
280
|
|
265
281
|
tags.concat(@default_tags)
|
266
282
|
|
283
|
+
service = kube_labels['app'] || kube_labels['k8s-app'] unless kube_labels.nil?
|
284
|
+
source = kube['pod_name']
|
285
|
+
source_category = kube['container_name']
|
286
|
+
|
287
|
+
service = @service if service.nil?
|
288
|
+
source = @source if source.nil?
|
289
|
+
source_category = @source_category if source_category.nil?
|
290
|
+
|
267
291
|
datetime = Time.at(Fluent::EventTime.new(time).to_r).utc.to_datetime
|
268
292
|
|
269
293
|
payload =
|
@@ -271,9 +295,9 @@ module Fluent::Plugin
|
|
271
295
|
logset: @logset,
|
272
296
|
msg: msg,
|
273
297
|
datetime: datetime,
|
274
|
-
service:
|
275
|
-
source:
|
276
|
-
source_category:
|
298
|
+
service: service,
|
299
|
+
source: source,
|
300
|
+
source_category: source_category,
|
277
301
|
tags: tags
|
278
302
|
)
|
279
303
|
|
@@ -290,12 +314,19 @@ module Fluent::Plugin
|
|
290
314
|
end
|
291
315
|
|
292
316
|
rescue => error
|
293
|
-
increment_failed_requests_count
|
294
|
-
increment_retried_entries_count(entries_count)
|
295
|
-
# RPC cancelled, so retry via re-raising the error.
|
296
|
-
@log.debug "Retrying #{entries_count} log message(s) later.",
|
297
|
-
error: error.to_s
|
298
317
|
raise error
|
318
|
+
increment_failed_requests_count
|
319
|
+
if entries_count.nil?
|
320
|
+
increment_dropped_entries_count(1)
|
321
|
+
@log.error 'Not retrying a log message later',
|
322
|
+
error: error.to_s
|
323
|
+
else
|
324
|
+
increment_retried_entries_count(entries_count)
|
325
|
+
# RPC cancelled, so retry via re-raising the error.
|
326
|
+
@log.debug "Retrying #{entries_count} log message(s) later.",
|
327
|
+
error: error.to_s
|
328
|
+
raise error
|
329
|
+
end
|
299
330
|
end
|
300
331
|
end
|
301
332
|
end
|
@@ -0,0 +1,549 @@
|
|
1
|
+
# Copyright 2017 Yusuke KUOKA All rights reserved.
|
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.
|
14
|
+
require 'erb'
|
15
|
+
require 'json'
|
16
|
+
require 'open-uri'
|
17
|
+
require 'socket'
|
18
|
+
require 'time'
|
19
|
+
require 'yaml'
|
20
|
+
require 'fluent/plugin/output'
|
21
|
+
require 'datadog/log'
|
22
|
+
|
23
|
+
require_relative 'monitoring'
|
24
|
+
|
25
|
+
module Fluent::Plugin
|
26
|
+
# fluentd output plugin for the Datadog Log Intake API
|
27
|
+
class DatadogOutput < ::Fluent::Plugin::Output
|
28
|
+
Fluent::Plugin.register_output('datadog', self)
|
29
|
+
|
30
|
+
helpers :compat_parameters, :inject
|
31
|
+
|
32
|
+
include ::Datadog::Log
|
33
|
+
|
34
|
+
DEFAULT_BUFFER_TYPE = 'memory'
|
35
|
+
|
36
|
+
PLUGIN_NAME = 'Fluentd Datadog plugin'
|
37
|
+
PLUGIN_VERSION = '0.1.0'
|
38
|
+
|
39
|
+
# Address of the metadata service.
|
40
|
+
METADATA_SERVICE_ADDR = '169.254.169.254'
|
41
|
+
|
42
|
+
# Disable this warning to conform to fluentd config_param conventions.
|
43
|
+
# rubocop:disable Style/HashSyntax
|
44
|
+
|
45
|
+
# see https://github.com/DataDog/datadog-log-agent/blob/db13b53dfdd036d43acfb15089a43eb31548f09f/pkg/logagent/logsagent.go#L26-L30
|
46
|
+
# see https://github.com/DataDog/datadog-log-agent/blob/db13b53dfdd036d43acfb15089a43eb31548f09f/pkg/config/config.go#L52-L56
|
47
|
+
config_param :log_dd_url, :string, default: 'intake.logs.datadoghq.com'
|
48
|
+
config_param :log_dd_port, :integer, default: 10516
|
49
|
+
config_param :skip_ssl_validation, default: false
|
50
|
+
config_param :api_key, :string, default: ''
|
51
|
+
config_param :logset, :string, default: 'main'
|
52
|
+
|
53
|
+
# e.g. ['env:prod', 'app:myapp']
|
54
|
+
# see https://github.com/DataDog/datadog-log-agent/blob/db13b53dfdd036d43acfb15089a43eb31548f09f/pkg/logagent/etc/conf.d/integration.yaml.example
|
55
|
+
config_param :tags, :array, default: [], value_type: :string
|
56
|
+
config_param :service, :string, default: '-'
|
57
|
+
# e.g. 'nginx'
|
58
|
+
config_param :source, :string, default: ''
|
59
|
+
config_param :source_category, :string, default: ''
|
60
|
+
|
61
|
+
config_section :buffer do
|
62
|
+
config_set_default :@type, DEFAULT_BUFFER_TYPE
|
63
|
+
end
|
64
|
+
|
65
|
+
# e.g. 'http_access'
|
66
|
+
# config_param :source_category, :string, default: ''
|
67
|
+
|
68
|
+
# Specify project/instance metadata.
|
69
|
+
#
|
70
|
+
# project_id, zone, and vm_id are required to have valid values, which
|
71
|
+
# can be obtained from the metadata service or set explicitly.
|
72
|
+
# Otherwise, the plugin will fail to initialize.
|
73
|
+
#
|
74
|
+
# Note that while 'project id' properly refers to the alphanumeric name
|
75
|
+
# of the project, the logging service will also accept the project number,
|
76
|
+
# so either one is acceptable in this context.
|
77
|
+
#
|
78
|
+
# Whether to attempt to obtain metadata from the local metadata service.
|
79
|
+
# It is safe to specify 'true' even on platforms with no metadata service.
|
80
|
+
config_param :use_metadata_service, :bool, :default => true
|
81
|
+
# These parameters override any values obtained from the metadata service.
|
82
|
+
config_param :project_id, :string, :default => nil
|
83
|
+
config_param :zone, :string, :default => nil
|
84
|
+
config_param :vm_id, :string, :default => nil
|
85
|
+
config_param :vm_name, :string, :default => nil
|
86
|
+
|
87
|
+
# TODO: Correlate log messages to corresponding Datadog APM spans
|
88
|
+
# config_param :trace_key, :string, :default => DEFAULT_TRACE_KEY
|
89
|
+
|
90
|
+
# Whether to try to detect if the record is a text log entry with JSON
|
91
|
+
# content that needs to be parsed.
|
92
|
+
config_param :detect_json, :bool, :default => false
|
93
|
+
|
94
|
+
# Whether to reject log entries with invalid tags. If this option is set to
|
95
|
+
# false, tags will be made valid by converting any non-string tag to a
|
96
|
+
# string, and sanitizing any non-utf8 or other invalid characters.
|
97
|
+
config_param :require_valid_tags, :bool, :default => false
|
98
|
+
|
99
|
+
# Whether to allow non-UTF-8 characters in user logs. If set to true, any
|
100
|
+
# non-UTF-8 character would be replaced by the string specified by
|
101
|
+
# 'non_utf8_replacement_string'. If set to false, any non-UTF-8 character
|
102
|
+
# would trigger the plugin to error out.
|
103
|
+
config_param :coerce_to_utf8, :bool, :default => true
|
104
|
+
|
105
|
+
# If 'coerce_to_utf8' is set to true, any non-UTF-8 character would be
|
106
|
+
# replaced by the string specified here.
|
107
|
+
config_param :non_utf8_replacement_string, :string, :default => ' '
|
108
|
+
|
109
|
+
# Whether to collect metrics about the plugin usage. The mechanism for
|
110
|
+
# collecting and exposing metrics is controlled by the monitoring_type
|
111
|
+
# parameter.
|
112
|
+
config_param :enable_monitoring, :bool, :default => false
|
113
|
+
config_param :monitoring_type, :string, :default => 'prometheus'
|
114
|
+
|
115
|
+
# rubocop:enable Style/HashSyntax
|
116
|
+
|
117
|
+
attr_reader :zone
|
118
|
+
attr_reader :vm_id
|
119
|
+
|
120
|
+
def initialize
|
121
|
+
super
|
122
|
+
# use the global logger
|
123
|
+
@log = $log # rubocop:disable Style/GlobalVars
|
124
|
+
end
|
125
|
+
|
126
|
+
def configure(conf)
|
127
|
+
compat_parameters_convert(conf, :buffer, :inject)
|
128
|
+
super
|
129
|
+
|
130
|
+
if @api_key.size == 0
|
131
|
+
@api_key = ENV['DD_LOG_API_KEY']
|
132
|
+
if @api_key == '' || @api_key.nil?
|
133
|
+
error_message = 'Unable to obtain api_key from DD_LOG_API_KEY'
|
134
|
+
fail Fluent::ConfigError, error_message
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# If monitoring is enabled, register metrics in the default registry
|
139
|
+
# and store metric objects for future use.
|
140
|
+
if @enable_monitoring
|
141
|
+
registry = Monitoring::MonitoringRegistryFactory.create @monitoring_type
|
142
|
+
@successful_requests_count = registry.counter(
|
143
|
+
:datadog_successful_requests_count,
|
144
|
+
'A number of successful requests to the Datadog Log Intake API')
|
145
|
+
@failed_requests_count = registry.counter(
|
146
|
+
:datadog_failed_requests_count,
|
147
|
+
'A number of failed requests to the Datadog Log Intake API,'\
|
148
|
+
' broken down by the error code')
|
149
|
+
@ingested_entries_count = registry.counter(
|
150
|
+
:datadog_ingested_entries_count,
|
151
|
+
'A number of log entries ingested by Datadog Log Intake')
|
152
|
+
@dropped_entries_count = registry.counter(
|
153
|
+
:datadog_dropped_entries_count,
|
154
|
+
'A number of log entries dropped by the Stackdriver output plugin')
|
155
|
+
@retried_entries_count = registry.counter(
|
156
|
+
:datadog_retried_entries_count,
|
157
|
+
'The number of log entries that failed to be ingested by the'\
|
158
|
+
' Stackdriver output plugin due to a transient error and were'\
|
159
|
+
' retried')
|
160
|
+
end
|
161
|
+
|
162
|
+
@platform = detect_platform
|
163
|
+
|
164
|
+
# Set required variables: @project_id, @vm_id, @vm_name and @zone.
|
165
|
+
set_required_metadata_variables
|
166
|
+
|
167
|
+
@default_tags = build_default_tags
|
168
|
+
|
169
|
+
# The resource and labels are now set up; ensure they can't be modified
|
170
|
+
# without first duping them.
|
171
|
+
@default_tags.freeze
|
172
|
+
|
173
|
+
# Log an informational message containing the Logs viewer URL
|
174
|
+
@log.info 'Logs viewer address: https://example.com/logs/'
|
175
|
+
end
|
176
|
+
|
177
|
+
def start
|
178
|
+
super
|
179
|
+
init_api_client
|
180
|
+
@successful_call = false
|
181
|
+
@timenanos_warning = false
|
182
|
+
end
|
183
|
+
|
184
|
+
def shutdown
|
185
|
+
super
|
186
|
+
@conn.shutdown
|
187
|
+
end
|
188
|
+
|
189
|
+
def format(tag, time, record)
|
190
|
+
record = inject_values_to_record(tag, time, record)
|
191
|
+
[tag, time, record].to_msgpack
|
192
|
+
end
|
193
|
+
|
194
|
+
def formatted_to_msgpack_binary?
|
195
|
+
true
|
196
|
+
end
|
197
|
+
|
198
|
+
def multi_workers_ready?
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
def write(chunk)
|
203
|
+
each_valid_record(chunk) do |_tag, time, record|
|
204
|
+
if @detect_json
|
205
|
+
# Save the timestamp and severity if available, then clear it out to
|
206
|
+
# allow for determining whether we should parse the log or message
|
207
|
+
# field.
|
208
|
+
timestamp = record.delete('time')
|
209
|
+
severity = record.delete('severity')
|
210
|
+
|
211
|
+
# If the log is json, we want to export it as a structured log
|
212
|
+
# unless there is additional metadata that would be lost.
|
213
|
+
record_json = nil
|
214
|
+
if record.length == 1
|
215
|
+
%w(log message msg).each do |field|
|
216
|
+
if record.key?(field)
|
217
|
+
record_json = parse_json_or_nil(record[field])
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
record = record_json unless record_json.nil?
|
222
|
+
# Restore timestamp and severity if necessary. Note that we don't
|
223
|
+
# want to override these keys in the JSON we've just parsed.
|
224
|
+
record['time'] ||= timestamp if timestamp
|
225
|
+
record['severity'] ||= severity if severity
|
226
|
+
end
|
227
|
+
|
228
|
+
# TODO: Correlate Datadog APM spans with log messages
|
229
|
+
# fq_trace_id = record.delete(@trace_key)
|
230
|
+
# entry.trace = fq_trace_id if fq_trace_id
|
231
|
+
|
232
|
+
begin
|
233
|
+
msg = nil
|
234
|
+
%w(log message msg).each do |field|
|
235
|
+
msg = record[field] if record.key?(field)
|
236
|
+
end
|
237
|
+
|
238
|
+
tags = []
|
239
|
+
|
240
|
+
kube = record['kubernetes'] || {}
|
241
|
+
|
242
|
+
mappings = {
|
243
|
+
'pod_name' => 'pod_name',
|
244
|
+
'container_name' => 'container_name',
|
245
|
+
'namespace_name' => 'kube_namespace'
|
246
|
+
}
|
247
|
+
|
248
|
+
mappings.each do |json_key, tag_key|
|
249
|
+
tags << "#{tag_key}=#{kube[json_key]}" if kube.key? json_key
|
250
|
+
end
|
251
|
+
|
252
|
+
if kube.key? 'labels'
|
253
|
+
labels = kube['labels']
|
254
|
+
labels.each do |k, v|
|
255
|
+
tags << "kube_#{k}=#{v}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# TODO: Include K8S tags like
|
260
|
+
# - kube_daemon_set=$daemonset_name
|
261
|
+
# - kube_deployment=$deployment_name
|
262
|
+
# - kube_replica_set=$replicaset_name
|
263
|
+
# -
|
264
|
+
|
265
|
+
tags.concat(@default_tags)
|
266
|
+
|
267
|
+
datetime = Time.at(Fluent::EventTime.new(time).to_r).utc.to_datetime
|
268
|
+
|
269
|
+
payload =
|
270
|
+
@conn.send_payload(
|
271
|
+
logset: @logset,
|
272
|
+
msg: msg,
|
273
|
+
datetime: datetime,
|
274
|
+
service: @service,
|
275
|
+
source: @source,
|
276
|
+
source_category: @source_category,
|
277
|
+
tags: tags
|
278
|
+
)
|
279
|
+
|
280
|
+
entries_count = 1
|
281
|
+
@log.debug 'Sent payload to Datadog.', payload: payload
|
282
|
+
increment_successful_requests_count
|
283
|
+
increment_ingested_entries_count(entries_count)
|
284
|
+
|
285
|
+
# Let the user explicitly know when the first call succeeded, to aid
|
286
|
+
# with verification and troubleshooting.
|
287
|
+
unless @successful_call
|
288
|
+
@successful_call = true
|
289
|
+
@log.info 'Successfully sent to Datadog.'
|
290
|
+
end
|
291
|
+
|
292
|
+
rescue => error
|
293
|
+
increment_failed_requests_count
|
294
|
+
increment_retried_entries_count(entries_count)
|
295
|
+
# RPC cancelled, so retry via re-raising the error.
|
296
|
+
@log.debug "Retrying #{entries_count} log message(s) later.",
|
297
|
+
error: error.to_s
|
298
|
+
raise error
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
def init_api_client
|
306
|
+
@conn = ::Datadog::Log::Client.new(
|
307
|
+
log_dd_url: @log_dd_uri,
|
308
|
+
log_dd_port: @log_dd_port,
|
309
|
+
api_key: @api_key,
|
310
|
+
hostname: @vm_id,
|
311
|
+
skip_ssl_validation: @skip_ssl_validation
|
312
|
+
)
|
313
|
+
end
|
314
|
+
|
315
|
+
def parse_json_or_nil(input)
|
316
|
+
# Only here to please rubocop...
|
317
|
+
return nil if input.nil?
|
318
|
+
|
319
|
+
input.each_codepoint do |c|
|
320
|
+
if c == 123
|
321
|
+
# left curly bracket (U+007B)
|
322
|
+
begin
|
323
|
+
return JSON.parse(input)
|
324
|
+
rescue JSON::ParserError
|
325
|
+
return nil
|
326
|
+
end
|
327
|
+
else
|
328
|
+
# Break (and return nil) unless the current character is whitespace,
|
329
|
+
# in which case we continue to look for a left curly bracket.
|
330
|
+
# Whitespace as per the JSON spec are: tabulation (U+0009),
|
331
|
+
# line feed (U+000A), carriage return (U+000D), and space (U+0020).
|
332
|
+
break unless c == 9 || c == 10 || c == 13 || c == 32
|
333
|
+
end # case
|
334
|
+
end # do
|
335
|
+
nil
|
336
|
+
end
|
337
|
+
|
338
|
+
# "enum" of Platform values
|
339
|
+
module Platform
|
340
|
+
OTHER = 0 # Other/unkown platform
|
341
|
+
GCE = 1 # Google Compute Engine
|
342
|
+
EC2 = 2 # Amazon EC2
|
343
|
+
end
|
344
|
+
|
345
|
+
# Determine what platform we are running on by consulting the metadata
|
346
|
+
# service (unless the user has explicitly disabled using that).
|
347
|
+
def detect_platform
|
348
|
+
unless @use_metadata_service
|
349
|
+
@log.info 'use_metadata_service is false; not detecting platform'
|
350
|
+
return Platform::OTHER
|
351
|
+
end
|
352
|
+
|
353
|
+
begin
|
354
|
+
open('http://' + METADATA_SERVICE_ADDR) do |f|
|
355
|
+
if f.meta['metadata-flavor'] == 'Google'
|
356
|
+
@log.info 'Detected GCE platform'
|
357
|
+
return Platform::GCE
|
358
|
+
end
|
359
|
+
if f.meta['server'] == 'EC2ws'
|
360
|
+
@log.info 'Detected EC2 platform'
|
361
|
+
return Platform::EC2
|
362
|
+
end
|
363
|
+
end
|
364
|
+
rescue StandardError => e
|
365
|
+
@log.error 'Failed to access metadata service: ', error: e
|
366
|
+
end
|
367
|
+
|
368
|
+
@log.info 'Unable to determine platform'
|
369
|
+
Platform::OTHER
|
370
|
+
end
|
371
|
+
|
372
|
+
def fetch_gce_metadata(metadata_path)
|
373
|
+
fail "Called fetch_gce_metadata with platform=#{@platform}" unless
|
374
|
+
@platform == Platform::GCE
|
375
|
+
# See https://cloud.google.com/compute/docs/metadata
|
376
|
+
open('http://' + METADATA_SERVICE_ADDR + '/computeMetadata/v1/' +
|
377
|
+
metadata_path, 'Metadata-Flavor' => 'Google', &:read)
|
378
|
+
end
|
379
|
+
|
380
|
+
# EC2 Metadata server returns everything in one call. Store it after the
|
381
|
+
# first fetch to avoid making multiple calls.
|
382
|
+
def ec2_metadata
|
383
|
+
fail "Called ec2_metadata with platform=#{@platform}" unless
|
384
|
+
@platform == Platform::EC2
|
385
|
+
unless @ec2_metadata
|
386
|
+
# See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
|
387
|
+
open('http://' + METADATA_SERVICE_ADDR +
|
388
|
+
'/latest/dynamic/instance-identity/document') do |f|
|
389
|
+
contents = f.read
|
390
|
+
@ec2_metadata = JSON.parse(contents)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
@ec2_metadata
|
395
|
+
end
|
396
|
+
|
397
|
+
# Set required variables like @vm_id, @vm_name and @zone.
|
398
|
+
def set_required_metadata_variables
|
399
|
+
set_vm_id
|
400
|
+
set_vm_name
|
401
|
+
set_zone
|
402
|
+
|
403
|
+
# All metadata parameters must now be set.
|
404
|
+
missing = []
|
405
|
+
missing << 'zone' unless @zone
|
406
|
+
missing << 'vm_id' unless @vm_id
|
407
|
+
missing << 'vm_name' unless @vm_name
|
408
|
+
return if missing.empty?
|
409
|
+
fail Fluent::ConfigError, 'Unable to obtain metadata parameters: ' +
|
410
|
+
missing.join(' ')
|
411
|
+
end
|
412
|
+
|
413
|
+
# 1. Return the value if it is explicitly set in the config already.
|
414
|
+
# 2. If not, try to retrieve it by calling metadata servers directly.
|
415
|
+
def set_vm_id
|
416
|
+
@vm_id ||= ec2_metadata['instanceId'] if @platform == Platform::EC2
|
417
|
+
rescue StandardError => e
|
418
|
+
@log.error 'Failed to obtain vm_id: ', error: e
|
419
|
+
end
|
420
|
+
|
421
|
+
# 1. Return the value if it is explicitly set in the config already.
|
422
|
+
# 2. If not, try to retrieve it locally.
|
423
|
+
def set_vm_name
|
424
|
+
@vm_name ||= Socket.gethostname
|
425
|
+
rescue StandardError => e
|
426
|
+
@log.error 'Failed to obtain vm name: ', error: e
|
427
|
+
end
|
428
|
+
|
429
|
+
# 1. Return the value if it is explicitly set in the config already.
|
430
|
+
# 2. If not, try to retrieve it locally.
|
431
|
+
def set_zone
|
432
|
+
@zone ||= 'aws:' + ec2_metadata['availabilityZone'] if
|
433
|
+
@platform == Platform::EC2 && ec2_metadata.key?('availabilityZone')
|
434
|
+
rescue StandardError => e
|
435
|
+
@log.error 'Failed to obtain location: ', error: e
|
436
|
+
end
|
437
|
+
|
438
|
+
# Determine agent level monitored resource labels based on the resource
|
439
|
+
# type. Each resource type has its own labels that need to be filled in.
|
440
|
+
def build_default_tags
|
441
|
+
aws_account_id = ec2_metadata['accountId'] if
|
442
|
+
ec2_metadata.key?('accountId')
|
443
|
+
# #host:i-09fbfed2672d2c6bf
|
444
|
+
%W(host=#{@vm_id} zone=#{@zone} aws_account_id=#{aws_account_id})
|
445
|
+
.concat @tags
|
446
|
+
end
|
447
|
+
|
448
|
+
# Filter out invalid non-Hash entries.
|
449
|
+
def each_valid_record(chunk)
|
450
|
+
chunk.msgpack_each do |event|
|
451
|
+
record = event.last
|
452
|
+
unless record.is_a?(Hash)
|
453
|
+
@log.warn 'Dropping log entries with malformed record: ' \
|
454
|
+
"'#{record.inspect}'. " \
|
455
|
+
'A log record should be in JSON format.'
|
456
|
+
next
|
457
|
+
end
|
458
|
+
tag = record.first
|
459
|
+
sanitized_tag = sanitize_tag(tag)
|
460
|
+
if sanitized_tag.nil?
|
461
|
+
@log.warn "Dropping log entries with invalid tag: '#{tag.inspect}'." \
|
462
|
+
' A tag should be a string with utf8 characters.'
|
463
|
+
next
|
464
|
+
end
|
465
|
+
yield event
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# Given a tag, returns the corresponding valid tag if possible, or nil if
|
470
|
+
# the tag should be rejected. If 'require_valid_tags' is false, non-string
|
471
|
+
# tags are converted to strings, and invalid characters are sanitized;
|
472
|
+
# otherwise such tags are rejected.
|
473
|
+
def sanitize_tag(tag)
|
474
|
+
if @require_valid_tags &&
|
475
|
+
(!tag.is_a?(String) || tag == '' || convert_to_utf8(tag) != tag)
|
476
|
+
return nil
|
477
|
+
end
|
478
|
+
tag = convert_to_utf8(tag.to_s)
|
479
|
+
tag = '_' if tag == ''
|
480
|
+
tag
|
481
|
+
end
|
482
|
+
|
483
|
+
# Encode as UTF-8. If 'coerce_to_utf8' is set to true in the config, any
|
484
|
+
# non-UTF-8 character would be replaced by the string specified by
|
485
|
+
# 'non_utf8_replacement_string'. If 'coerce_to_utf8' is set to false, any
|
486
|
+
# non-UTF-8 character would trigger the plugin to error out.
|
487
|
+
def convert_to_utf8(input)
|
488
|
+
if @coerce_to_utf8
|
489
|
+
input.encode(
|
490
|
+
'utf-8',
|
491
|
+
invalid: :replace,
|
492
|
+
undef: :replace,
|
493
|
+
replace: @non_utf8_replacement_string)
|
494
|
+
else
|
495
|
+
begin
|
496
|
+
input.encode('utf-8')
|
497
|
+
rescue EncodingError
|
498
|
+
@log.error 'Encountered encoding issues potentially due to non ' \
|
499
|
+
'UTF-8 characters. To allow non-UTF-8 characters and ' \
|
500
|
+
'replace them with spaces, please set "coerce_to_utf8" ' \
|
501
|
+
'to true.'
|
502
|
+
raise
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def ensure_array(value)
|
508
|
+
Array.try_convert(value) || (fail JSON::ParserError, "#{value.class}")
|
509
|
+
end
|
510
|
+
|
511
|
+
def ensure_hash(value)
|
512
|
+
Hash.try_convert(value) || (fail JSON::ParserError, "#{value.class}")
|
513
|
+
end
|
514
|
+
|
515
|
+
# Increment the metric for the number of successful requests.
|
516
|
+
def increment_successful_requests_count
|
517
|
+
return unless @successful_requests_count
|
518
|
+
@successful_requests_count.increment
|
519
|
+
end
|
520
|
+
|
521
|
+
# Increment the metric for the number of failed requests, labeled by
|
522
|
+
# the provided status code.
|
523
|
+
def increment_failed_requests_count
|
524
|
+
return unless @failed_requests_count
|
525
|
+
@failed_requests_count.increment
|
526
|
+
end
|
527
|
+
|
528
|
+
# Increment the metric for the number of log entries, successfully
|
529
|
+
# ingested by the Datadog Log Intake API.
|
530
|
+
def increment_ingested_entries_count(count)
|
531
|
+
return unless @ingested_entries_count
|
532
|
+
@ingested_entries_count.increment({}, count)
|
533
|
+
end
|
534
|
+
|
535
|
+
# Increment the metric for the number of log entries that were dropped
|
536
|
+
# and not ingested by the Datadog Log Intake API.
|
537
|
+
def increment_dropped_entries_count(count)
|
538
|
+
return unless @dropped_entries_count
|
539
|
+
@dropped_entries_count.increment({}, count)
|
540
|
+
end
|
541
|
+
|
542
|
+
# Increment the metric for the number of log entries that were dropped
|
543
|
+
# and not ingested by the Datadog Log Intake API.
|
544
|
+
def increment_retried_entries_count(count)
|
545
|
+
return unless @retried_entries_count
|
546
|
+
@retried_entries_count.increment({}, count)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
Binary file
|
Binary file
|
@@ -35,7 +35,70 @@ class DatadogLogOutputTest < Test::Unit::TestCase
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
def test_configure_with_env
|
39
|
+
new_stub_context do
|
40
|
+
setup_ec2_metadata_stubs
|
41
|
+
|
42
|
+
ENV.stubs(:[])
|
43
|
+
.with('DD_API_KEY')
|
44
|
+
.returns('myapikey_from_env')
|
45
|
+
|
46
|
+
ENV.stubs(:[])
|
47
|
+
.with(Not equals 'DD_API_KEY')
|
48
|
+
.returns('')
|
49
|
+
.times(3)
|
50
|
+
|
51
|
+
d = create_driver(<<-EOC)
|
52
|
+
type datadog_log
|
53
|
+
service myservice
|
54
|
+
source mysource
|
55
|
+
EOC
|
56
|
+
|
57
|
+
assert_equal 'myapikey_from_env', d.instance.api_key
|
58
|
+
assert_equal 'myservice', d.instance.service
|
59
|
+
assert_equal 'mysource', d.instance.source
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
38
63
|
def test_write
|
64
|
+
new_stub_context do
|
65
|
+
setup_ec2_metadata_stubs
|
66
|
+
|
67
|
+
timestamp_str = '2006-01-02T15:04:05.000000+00:00'
|
68
|
+
t = DateTime.rfc3339(timestamp_str).to_time
|
69
|
+
time = Fluent::EventTime.from_time(t)
|
70
|
+
d = create_driver(<<-EOC)
|
71
|
+
type datadog_log
|
72
|
+
api_key myapikey
|
73
|
+
service myservice
|
74
|
+
source mysource
|
75
|
+
source_category mysourcecategory
|
76
|
+
logset mylogset
|
77
|
+
log_level debug
|
78
|
+
EOC
|
79
|
+
conn = StubConn.new
|
80
|
+
fluentd_tag = 'mytag'
|
81
|
+
Net::TCPClient.stubs(:new)
|
82
|
+
.with(server: ':10516', ssl: true)
|
83
|
+
.returns(conn)
|
84
|
+
d.run(default_tag: fluentd_tag) do
|
85
|
+
record = {
|
86
|
+
'log' => 'mymsg'
|
87
|
+
}
|
88
|
+
d.feed(time, record)
|
89
|
+
end
|
90
|
+
|
91
|
+
# fail d.logs.inspect
|
92
|
+
assert_equal(1, d.logs.count { |l| l =~ /Sent payload to Datadog/ })
|
93
|
+
assert_equal(1, conn.sent.size)
|
94
|
+
# rubocop:disable LineLength
|
95
|
+
payload = %(myapikey/mylogset <46>0 2006-01-02T15:04:05.000000+00:00 i-81c16767 myservice - - [dd ddsource="mysource"][dd ddsourcecategory="mysourcecategory"][dd ddtags="host=i-81c16767,zone=aws:us-west-2b,aws_account_id=123456789012"] mymsg\n)
|
96
|
+
# rubocop:enable LineLength
|
97
|
+
assert_equal(payload, conn.sent.first)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_write_kube
|
39
102
|
new_stub_context do
|
40
103
|
setup_ec2_metadata_stubs
|
41
104
|
|
@@ -59,12 +122,20 @@ class DatadogLogOutputTest < Test::Unit::TestCase
|
|
59
122
|
d.run(default_tag: fluentd_tag) do
|
60
123
|
record = {
|
61
124
|
'log' => 'mymsg',
|
125
|
+
'docker' => {
|
126
|
+
'container_id' => 'myfullcontainerid'
|
127
|
+
},
|
62
128
|
'kubernetes' => {
|
63
129
|
'namespace' => 'myns',
|
64
130
|
'pod_name' => 'mypod',
|
65
131
|
'container_name' => 'mycontainer',
|
66
132
|
'labels' => {
|
67
133
|
'k8s-app' => 'myapp'
|
134
|
+
},
|
135
|
+
'annotations' => {
|
136
|
+
# rubocop:disable LineLength
|
137
|
+
'kubernetes.io/created-by' => '{"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"Deployment","namespace":"default","name":"myapp","uid":"d67e8857-c2dc-11e7-aed9-066d23381f8c","apiVersion":"extensions","resourceVersion":"289"}}'
|
138
|
+
# rubocop:enable LineLength
|
68
139
|
}
|
69
140
|
}
|
70
141
|
}
|
@@ -75,7 +146,7 @@ class DatadogLogOutputTest < Test::Unit::TestCase
|
|
75
146
|
assert_equal(1, d.logs.count { |l| l =~ /Sent payload to Datadog/ })
|
76
147
|
assert_equal(1, conn.sent.size)
|
77
148
|
# rubocop:disable LineLength
|
78
|
-
payload = %(myapikey/mylogset <46>0 2006-01-02T15:04:05.000000+00:00 i-81c16767
|
149
|
+
payload = %(myapikey/mylogset <46>0 2006-01-02T15:04:05.000000+00:00 i-81c16767 myapp - - [dd ddsource="mypod"][dd ddsourcecategory="mycontainer"][dd ddtags="pod_name=mypod,container_name=mycontainer,kube_k8s-app=myapp,kube_deployment=myapp,host=i-81c16767,zone=aws:us-west-2b,aws_account_id=123456789012"] mymsg\n)
|
79
150
|
# rubocop:enable LineLength
|
80
151
|
assert_equal(payload, conn.sent.first)
|
81
152
|
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# Copyright 2017 Yusuke KUOKA All rights reserved.
|
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.
|
14
|
+
|
15
|
+
require_relative 'base_test'
|
16
|
+
|
17
|
+
# Unit tests for Datadog Log plugin
|
18
|
+
class DatadogLogOutputTest < Test::Unit::TestCase
|
19
|
+
include BaseTest
|
20
|
+
|
21
|
+
def test_configure
|
22
|
+
new_stub_context do
|
23
|
+
setup_ec2_metadata_stubs
|
24
|
+
|
25
|
+
d = create_driver(<<-EOC)
|
26
|
+
type datadog_log
|
27
|
+
api_key myapikey
|
28
|
+
service myservice
|
29
|
+
source mysource
|
30
|
+
EOC
|
31
|
+
|
32
|
+
assert_equal 'myapikey', d.instance.api_key
|
33
|
+
assert_equal 'myservice', d.instance.service
|
34
|
+
assert_equal 'mysource', d.instance.source
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_write
|
39
|
+
new_stub_context do
|
40
|
+
setup_ec2_metadata_stubs
|
41
|
+
|
42
|
+
timestamp_str = '2006-01-02T15:04:05.000000+00:00'
|
43
|
+
t = DateTime.rfc3339(timestamp_str).to_time
|
44
|
+
time = Fluent::EventTime.from_time(t)
|
45
|
+
d = create_driver(<<-EOC)
|
46
|
+
type datadog_log
|
47
|
+
api_key myapikey
|
48
|
+
service myservice
|
49
|
+
source mysource
|
50
|
+
source_category mysourcecategory
|
51
|
+
logset mylogset
|
52
|
+
log_level debug
|
53
|
+
EOC
|
54
|
+
conn = StubConn.new
|
55
|
+
fluentd_tag = 'mytag'
|
56
|
+
Net::TCPClient.stubs(:new)
|
57
|
+
.with(server: ':10516', ssl: true)
|
58
|
+
.returns(conn)
|
59
|
+
d.run(default_tag: fluentd_tag) do
|
60
|
+
record = {
|
61
|
+
'log' => 'mymsg',
|
62
|
+
'kubernetes' => {
|
63
|
+
'namespace' => 'myns',
|
64
|
+
'pod_name' => 'mypod',
|
65
|
+
'container_name' => 'mycontainer',
|
66
|
+
'labels' => {
|
67
|
+
'k8s-app' => 'myapp'
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
d.feed(time, record)
|
72
|
+
end
|
73
|
+
|
74
|
+
# fail d.logs.inspect
|
75
|
+
assert_equal(1, d.logs.count { |l| l =~ /Sent payload to Datadog/ })
|
76
|
+
assert_equal(1, conn.sent.size)
|
77
|
+
# rubocop:disable LineLength
|
78
|
+
payload = %(myapikey/mylogset <46>0 2006-01-02T15:04:05.000000+00:00 i-81c16767 myservice - - [dd ddsource="mysource"][dd ddsourcecategory="mysourcecategory"][dd ddtags="pod_name=mypod,container_name=mycontainer,kube_k8s-app=myapp,host=i-81c16767,zone=aws:us-west-2b,aws_account_id=123456789012"] mymsg\n)
|
79
|
+
# rubocop:enable LineLength
|
80
|
+
assert_equal(payload, conn.sent.first)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_prometheus_metrics
|
85
|
+
new_stub_context do
|
86
|
+
setup_ec2_metadata_stubs
|
87
|
+
timestamp_str = '2006-01-02T15:04:05.000000+00:00'
|
88
|
+
t = DateTime.rfc3339(timestamp_str).to_time
|
89
|
+
time = Fluent::EventTime.from_time(t)
|
90
|
+
[
|
91
|
+
# Single successful request.
|
92
|
+
[false, 0, 1, 1, [1, 0, 1, 0, 0]],
|
93
|
+
# Several successful requests.
|
94
|
+
[false, 0, 2, 1, [2, 0, 2, 0, 0]]
|
95
|
+
].each do |_should_fail, _code, request_count, entry_count, metric_values|
|
96
|
+
setup_prometheus
|
97
|
+
(1..request_count).each do
|
98
|
+
d = create_driver(<<-EOC)
|
99
|
+
type datadog_log
|
100
|
+
api_key myapikey
|
101
|
+
service myservice
|
102
|
+
source mysource
|
103
|
+
source_category mysourcecategory
|
104
|
+
logset mylogset
|
105
|
+
log_level debug
|
106
|
+
enable_monitoring true
|
107
|
+
EOC
|
108
|
+
conn = StubConn.new
|
109
|
+
Net::TCPClient.stubs(:new)
|
110
|
+
.with(server: ':10516', ssl: true)
|
111
|
+
.returns(conn)
|
112
|
+
d.run(default_tag: 'mytag') do
|
113
|
+
(1..entry_count).each do |i|
|
114
|
+
d.feed time, 'message' => log_entry(i.to_s)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
successful_requests_count, failed_requests_count,
|
119
|
+
ingested_entries_count, dropped_entries_count,
|
120
|
+
retried_entries_count = metric_values
|
121
|
+
assert_prometheus_metric_value(:datadog_successful_requests_count,
|
122
|
+
successful_requests_count)
|
123
|
+
assert_prometheus_metric_value(:datadog_failed_requests_count,
|
124
|
+
failed_requests_count)
|
125
|
+
assert_prometheus_metric_value(:datadog_ingested_entries_count,
|
126
|
+
ingested_entries_count)
|
127
|
+
assert_prometheus_metric_value(:datadog_dropped_entries_count,
|
128
|
+
dropped_entries_count)
|
129
|
+
assert_prometheus_metric_value(:datadog_retried_entries_count,
|
130
|
+
retried_entries_count)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_struct_payload_non_utf8_log
|
136
|
+
# d.emit('msg' => log_entry(0),
|
137
|
+
# 'normal_key' => "test#{non_utf8_character}non utf8",
|
138
|
+
# "non_utf8#{non_utf8_character}key" => 5000,
|
139
|
+
# 'nested_struct' => { "non_utf8#{non_utf8_character}key" => \
|
140
|
+
# "test#{non_utf8_character}non utf8" },
|
141
|
+
# 'null_field' => nil)
|
142
|
+
end
|
143
|
+
|
144
|
+
class StubConn
|
145
|
+
attr_reader :sent
|
146
|
+
|
147
|
+
def initialize
|
148
|
+
@sent = []
|
149
|
+
end
|
150
|
+
|
151
|
+
def write(payload)
|
152
|
+
@sent << payload
|
153
|
+
end
|
154
|
+
|
155
|
+
def close
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# Use the right single quotation mark as the sample non-utf8 character.
|
162
|
+
def non_utf8_character
|
163
|
+
[0x92].pack('C*')
|
164
|
+
end
|
165
|
+
|
166
|
+
# For an optional field with default values, Protobuf omits the field when it
|
167
|
+
# is deserialized to json. So we need to add an extra check for gRPC which
|
168
|
+
# uses Protobuf.
|
169
|
+
#
|
170
|
+
# An optional block can be passed in if we need to assert something other than
|
171
|
+
# a plain equal. e.g. assert_in_delta.
|
172
|
+
def assert_equal_with_default(field, expected_value, default_value, entry)
|
173
|
+
if expected_value == default_value
|
174
|
+
assert_nil field
|
175
|
+
elsif block_given?
|
176
|
+
yield
|
177
|
+
else
|
178
|
+
assert_equal expected_value, field, entry
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Get the fields of the payload.
|
183
|
+
def get_fields(payload)
|
184
|
+
payload['fields']
|
185
|
+
end
|
186
|
+
|
187
|
+
# Get the value of a struct field.
|
188
|
+
def get_struct(field)
|
189
|
+
field['structValue']
|
190
|
+
end
|
191
|
+
|
192
|
+
# Get the value of a string field.
|
193
|
+
def get_string(field)
|
194
|
+
field['stringValue']
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get the value of a number field.
|
198
|
+
def get_number(field)
|
199
|
+
field['numberValue']
|
200
|
+
end
|
201
|
+
|
202
|
+
# The null value.
|
203
|
+
def null_value
|
204
|
+
{ 'nullValue' => 'NULL_VALUE' }
|
205
|
+
end
|
206
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-datadog-log
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yusuke KUOKA
|
@@ -156,12 +156,16 @@ files:
|
|
156
156
|
- lib/datadog/log.rb
|
157
157
|
- lib/fluent/plugin/monitoring.rb
|
158
158
|
- lib/fluent/plugin/out_datadog_log.rb
|
159
|
+
- lib/fluent/plugin/out_datadog_log.rb~
|
159
160
|
- pkg/fluent-plugin-datadog-0.1.0.gem
|
160
161
|
- pkg/fluent-plugin-datadog-log-0.1.0.gem
|
162
|
+
- pkg/fluent-plugin-datadog-log-0.1.0.rc1.gem
|
163
|
+
- pkg/fluent-plugin-datadog-log-0.1.0.rc2.gem
|
161
164
|
- test/helper.rb
|
162
165
|
- test/plugin/base_test.rb
|
163
166
|
- test/plugin/constants.rb
|
164
167
|
- test/plugin/test_out_datadog_log.rb
|
168
|
+
- test/plugin/test_out_datadog_log.rb~
|
165
169
|
homepage: https://github.com/mumoshu/fluent-plugin-datadog-log
|
166
170
|
licenses:
|
167
171
|
- Apache-2.0
|
@@ -191,3 +195,4 @@ test_files:
|
|
191
195
|
- test/plugin/base_test.rb
|
192
196
|
- test/plugin/constants.rb
|
193
197
|
- test/plugin/test_out_datadog_log.rb
|
198
|
+
- test/plugin/test_out_datadog_log.rb~
|