logstash-input-oss 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTORS +10 -0
- data/DEVELOPER.md +10 -0
- data/Gemfile +2 -0
- data/LICENSE +11 -0
- data/README.md +143 -0
- data/lib/com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar +0 -0
- data/lib/com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar +0 -0
- data/lib/com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar +0 -0
- data/lib/com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar +0 -0
- data/lib/com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar +0 -0
- data/lib/commons-codec/commons-codec/1.9/commons-codec-1.9.jar +0 -0
- data/lib/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
- data/lib/javax/activation/activation/1.1/activation-1.1.jar +0 -0
- data/lib/javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar +0 -0
- data/lib/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar +0 -0
- data/lib/logstash-input-oss_jars.rb +54 -0
- data/lib/logstash/inputs/mns/message.rb +53 -0
- data/lib/logstash/inputs/mns/request.rb +85 -0
- data/lib/logstash/inputs/oss.rb +382 -0
- data/lib/logstash/inputs/version.rb +14 -0
- data/lib/org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar +0 -0
- data/lib/org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar +0 -0
- data/lib/org/codehaus/jettison/jettison/1.1/jettison-1.1.jar +0 -0
- data/lib/org/jdom/jdom/1.1/jdom-1.1.jar +0 -0
- data/lib/org/json/json/20170516/json-20170516.jar +0 -0
- data/lib/stax/stax-api/1.0.1/stax-api-1.0.1.jar +0 -0
- data/logstash-input-oss.gemspec +33 -0
- data/spec/integration/common.rb +79 -0
- data/spec/integration/oss_spec.rb +73 -0
- data/spec/sample/uncompressed.log +37 -0
- data/spec/sample/uncompressed.log.1.gz +0 -0
- metadata +249 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f3623c612fe4e3a2267316232b1eb5af798ae26656d1299eb5508582a3982d9c
|
4
|
+
data.tar.gz: 07a9bfe7d7ae1ad99f300ec7020a612f1570d05ddfaed4daa2abb517eecfc2f2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fea65a2a8690cbce0d40a86843e2ff9457d8e042a28074d30238b5ffa01098264adcc5912669304ffb55b4dc49cb1ebba69f48691d18aa4bb64211dd18488e5
|
7
|
+
data.tar.gz: 55ba1325ca09e9a1f4fe51fce2bc976d30dcba1c1d7d6e70618e3406fc71249be34966843bf86fb4dc8e190ce2ac0316b19e12f95e02374228969d1c923cf79a
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
The following is a list of people who have contributed ideas, code, bug
|
2
|
+
reports, or in general have helped logstash along its way.
|
3
|
+
|
4
|
+
Contributors:
|
5
|
+
* jinhu.wu - jinhu.wu.nju@gmail.com
|
6
|
+
|
7
|
+
Note: If you've sent us patches, bug reports, or otherwise contributed to
|
8
|
+
Logstash, and you aren't on the list above and want to be, please let us know
|
9
|
+
and we'll make sure you're here. Contributions from folks like you are what make
|
10
|
+
open source awesome.
|
data/DEVELOPER.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Running the tests
|
2
|
+
```bash
|
3
|
+
bundle install
|
4
|
+
bundle exec rspec
|
5
|
+
```
|
6
|
+
|
7
|
+
If you want to run the integration tests against a real bucket you need to pass your Aliyun credentials to the test runner or declare it in your environment.
|
8
|
+
```bash
|
9
|
+
OSS_ENDPOINT="Aliyun OSS endpoint to connect to" OSS_ACCESS_KEY="Your access key id" OSS_SECRET_KEY="Your access secret" OSS_BUCKET="Your bucket" MNS_ENDPOINT="MNS endpoint" MNS_QUEUE="MNS queue" BACKUP_DIR="/tmp/oss" BACKUP_BUCKET="Your backup bucket" bundle exec rspec spec/integration/oss_spec.rb --tag integration
|
10
|
+
```
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
you may not use this file except in compliance with the License.
|
3
|
+
You may obtain a copy of the License at
|
4
|
+
|
5
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
See the License for the specific language governing permissions and
|
11
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Logstash OSS Input Plugin
|
2
|
+
|
3
|
+
This is a plugin for [Logstash](https://github.com/elastic/logstash).
|
4
|
+
|
5
|
+
It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
|
6
|
+
|
7
|
+
## Documentation
|
8
|
+
|
9
|
+
This plugin reads data from OSS periodically.
|
10
|
+
|
11
|
+
This plugin uses MNS on the same region of the OSS bucket. We must setup MNS and OSS event notification before using this plugin.
|
12
|
+
|
13
|
+
[This document](https://help.aliyun.com/document_detail/52656.html) shows how to setup MNS and OSS event notification.
|
14
|
+
|
15
|
+
This plugin will poll events from MNS queue and extract object keys from these events, and then will read those objects from OSS.
|
16
|
+
|
17
|
+
### Usage:
|
18
|
+
This is an example of logstash config:
|
19
|
+
|
20
|
+
```
|
21
|
+
input {
|
22
|
+
oss {
|
23
|
+
"endpoint" => "OSS endpoint to connect to" (required)
|
24
|
+
"bucket" => "Your bucket name" (required)
|
25
|
+
"access_key_id" => "Your access key id" (required)
|
26
|
+
"access_key_secret" => "Your secret key" (required)
|
27
|
+
"backup_to_bucket" => "Your backup bucket name" (optional, default = nil)
|
28
|
+
"prefix" => "sample" (optional, default = nil)
|
29
|
+
"delete" => false (optional, default = false)
|
30
|
+
"exclude_pattern" => "^sample-logstash" (optional, default = nil)
|
31
|
+
"backup_add_prefix" => "logstash-input/" (optional, default = nil)
|
32
|
+
"include_object_properties" => true (optional, default = false)
|
33
|
+
"additional_oss_settings" => {
|
34
|
+
"max_connections_to_oss" => 1024 (optional, default = 1024)
|
35
|
+
"secure_connection_enabled" => false (optional, default = false)
|
36
|
+
}
|
37
|
+
"mns_settings" => { (required)
|
38
|
+
"endpoint" => "MNS endpoint to connect to" (required)
|
39
|
+
"queue" => "MNS queue name" (required)
|
40
|
+
"poll_interval_seconds" => 10 (optional, default = 10)
|
41
|
+
"wait_seconds" => 3 (optional, default = nil)
|
42
|
+
}
|
43
|
+
codec => json {
|
44
|
+
charset => "UTF-8"
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
```
|
49
|
+
### Logstash OSS Input Configuration Options
|
50
|
+
This plugin supports the following configuration options
|
51
|
+
|
52
|
+
Note: Files end with `.gz` or `.gzip` are handled as gzip'ed files, others are handled as plain files.
|
53
|
+
|
54
|
+
|Configuration|Type|Required|Comments|
|
55
|
+
|:---:|:---:|:---:|:---|
|
56
|
+
|endpoint|string|Yes|OSS endpoint to connect|
|
57
|
+
|bucket|string|Yes|Your OSS bucket name|
|
58
|
+
|access_key_id|string|Yes|Your access key id|
|
59
|
+
|access_key_secret|string|Yes|Your access secret key|
|
60
|
+
|prefix|string|No|If specified, the prefix of filenames in the bucket must match (not a regexp)|
|
61
|
+
|additional_oss_settings|hash|No|Additional oss client configurations, valid keys are: `secure_connection_enabled` and `max_connections_to_oss`|
|
62
|
+
|delete|boolean|No|Whether to delete processed files from the original bucket|
|
63
|
+
|backup_to_bucket|string|No|Name of an OSS bucket to backup processed files to|
|
64
|
+
|backup_to_dir|string|No|Path of a local directory to backup processed files to|
|
65
|
+
|backup_add_prefix|string|No|Append a prefix to the key (full path including file name in OSS) after processing(If backing up to another (or the same) bucket, this effectively lets you choose a new 'folder' to place the files in)|
|
66
|
+
|include_object_properties|boolean|No|Whether or not to include the OSS object's properties (last_modified, content_type, metadata) into each Event at [@metadata][oss]. Regardless of this setting, [@metadata][oss][key] will always be present|
|
67
|
+
|exclude_pattern|string|No|Ruby style regexp of keys to exclude from the bucket|
|
68
|
+
|mns_settings|hash|Yes|MNS settings, valid keys are: `endpoint`, `queue`, `poll_interval_seconds`, `wait_seconds`|
|
69
|
+
|
70
|
+
#### mns_settings
|
71
|
+
|
72
|
+
[MNS consume messages](https://help.aliyun.com/document_detail/35136.html)
|
73
|
+
|
74
|
+
* endpoint
|
75
|
+
* queue
|
76
|
+
* wait_seconds
|
77
|
+
* poll_interval_seconds Poll messages interval from MNS if there is no message this time, default 10 seconds
|
78
|
+
|
79
|
+
For more details about mns configurations, please view MNS documentation in the link above.
|
80
|
+
|
81
|
+
## Need Help?
|
82
|
+
|
83
|
+
Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
|
84
|
+
|
85
|
+
## Developing
|
86
|
+
|
87
|
+
### 1. Plugin Development and Testing
|
88
|
+
|
89
|
+
#### Code
|
90
|
+
- To get started, you'll need JRuby with the Bundler gem installed.
|
91
|
+
|
92
|
+
- Install dependencies
|
93
|
+
```sh
|
94
|
+
bundle install
|
95
|
+
```
|
96
|
+
|
97
|
+
#### Test
|
98
|
+
|
99
|
+
- Update your dependencies
|
100
|
+
|
101
|
+
```sh
|
102
|
+
bundle install
|
103
|
+
```
|
104
|
+
|
105
|
+
- Run tests
|
106
|
+
|
107
|
+
```sh
|
108
|
+
bundle exec rspec
|
109
|
+
```
|
110
|
+
|
111
|
+
### 2. Running your unpublished Plugin in Logstash
|
112
|
+
|
113
|
+
#### 2.1 Run in an installed Logstash
|
114
|
+
|
115
|
+
you can build the gem and install it using:
|
116
|
+
|
117
|
+
- Build your plugin gem
|
118
|
+
|
119
|
+
```sh
|
120
|
+
gem build logstash-input-oss.gemspec
|
121
|
+
```
|
122
|
+
|
123
|
+
- Install the plugin from the Logstash home
|
124
|
+
|
125
|
+
```sh
|
126
|
+
bin/logstash-plugin install /path/to/logstash-input-oss-0.0.1-java.gem
|
127
|
+
```
|
128
|
+
|
129
|
+
- Start Logstash and proceed to test the plugin
|
130
|
+
|
131
|
+
```bash
|
132
|
+
./bin/logstash -f config/logstash-sample.conf
|
133
|
+
```
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
|
137
|
+
All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
|
138
|
+
|
139
|
+
Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
|
140
|
+
|
141
|
+
It is more important to the community that you are able to contribute.
|
142
|
+
|
143
|
+
For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# this is a generated file, to avoid over-writing it just delete this comment
|
2
|
+
begin
|
3
|
+
require 'jar_dependencies'
|
4
|
+
rescue LoadError
|
5
|
+
require 'org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar'
|
6
|
+
require 'com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar'
|
7
|
+
require 'commons-codec/commons-codec/1.9/commons-codec-1.9.jar'
|
8
|
+
require 'org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar'
|
9
|
+
require 'com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar'
|
10
|
+
require 'org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar'
|
11
|
+
require 'javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar'
|
12
|
+
require 'com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar'
|
13
|
+
require 'org/codehaus/jettison/jettison/1.1/jettison-1.1.jar'
|
14
|
+
require 'com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar'
|
15
|
+
require 'javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar'
|
16
|
+
require 'org/json/json/20170516/json-20170516.jar'
|
17
|
+
require 'com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar'
|
18
|
+
require 'commons-logging/commons-logging/1.2/commons-logging-1.2.jar'
|
19
|
+
require 'org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar'
|
20
|
+
require 'javax/activation/activation/1.1/activation-1.1.jar'
|
21
|
+
require 'com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar'
|
22
|
+
require 'org/jdom/jdom/1.1/jdom-1.1.jar'
|
23
|
+
require 'org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar'
|
24
|
+
require 'org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar'
|
25
|
+
require 'com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar'
|
26
|
+
require 'com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar'
|
27
|
+
require 'stax/stax-api/1.0.1/stax-api-1.0.1.jar'
|
28
|
+
end
|
29
|
+
|
30
|
+
if defined? Jars
|
31
|
+
require_jar 'org.apache.httpcomponents', 'httpcore', '4.4.1'
|
32
|
+
require_jar 'com.aliyun.oss', 'aliyun-sdk-oss', '3.4.0'
|
33
|
+
require_jar 'commons-codec', 'commons-codec', '1.9'
|
34
|
+
require_jar 'org.codehaus.jackson', 'jackson-xc', '1.8.3'
|
35
|
+
require_jar 'com.aliyun', 'aliyun-java-sdk-ram', '3.0.0'
|
36
|
+
require_jar 'org.apache.httpcomponents', 'httpclient', '4.4.1'
|
37
|
+
require_jar 'javax.xml.bind', 'jaxb-api', '2.2.2'
|
38
|
+
require_jar 'com.sun.jersey', 'jersey-json', '1.9'
|
39
|
+
require_jar 'org.codehaus.jettison', 'jettison', '1.1'
|
40
|
+
require_jar 'com.sun.jersey', 'jersey-core', '1.9'
|
41
|
+
require_jar 'javax.xml.stream', 'stax-api', '1.0-2'
|
42
|
+
require_jar 'org.json', 'json', '20170516'
|
43
|
+
require_jar 'com.aliyun', 'aliyun-java-sdk-core', '3.4.0'
|
44
|
+
require_jar 'commons-logging', 'commons-logging', '1.2'
|
45
|
+
require_jar 'org.codehaus.jackson', 'jackson-jaxrs', '1.8.3'
|
46
|
+
require_jar 'javax.activation', 'activation', '1.1'
|
47
|
+
require_jar 'com.sun.xml.bind', 'jaxb-impl', '2.2.3-1'
|
48
|
+
require_jar 'org.jdom', 'jdom', '1.1'
|
49
|
+
require_jar 'org.codehaus.jackson', 'jackson-mapper-asl', '1.8.3'
|
50
|
+
require_jar 'org.codehaus.jackson', 'jackson-core-asl', '1.8.3'
|
51
|
+
require_jar 'com.aliyun', 'aliyun-java-sdk-ecs', '4.2.0'
|
52
|
+
require_jar 'com.aliyun', 'aliyun-java-sdk-sts', '3.0.0'
|
53
|
+
require_jar 'stax', 'stax-api', '1.0.1'
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module LogStash
|
5
|
+
module Inputs
|
6
|
+
module MNS
|
7
|
+
# Class for Aliyun MNS Message.
|
8
|
+
class Message
|
9
|
+
include REXML
|
10
|
+
|
11
|
+
attr_reader :queue, :id, :body_md5, :body, :receipt_handle, :enqueue_at,
|
12
|
+
:first_enqueue_at, :next_visible_at, :dequeue_count, :priority
|
13
|
+
|
14
|
+
def initialize(queue, content)
|
15
|
+
@queue = queue
|
16
|
+
|
17
|
+
doc = Document.new(content)
|
18
|
+
doc.elements[1].each do |e|
|
19
|
+
if e.node_type == :element
|
20
|
+
if e.name == 'MessageId'
|
21
|
+
@id = e.text
|
22
|
+
elsif e.name == 'MessageBodyMD5'
|
23
|
+
@body_md5 = e.text
|
24
|
+
elsif e.name == 'MessageBody'
|
25
|
+
@body = e.text
|
26
|
+
elsif e.name == 'EnqueueTime'
|
27
|
+
@enqueue_at = e.text.to_i
|
28
|
+
elsif e.name == 'FirstDequeueTime'
|
29
|
+
@first_enqueue_at = e.text.to_i
|
30
|
+
elsif e.name == 'DequeueCount'
|
31
|
+
@dequeue_count = e.text.to_i
|
32
|
+
elsif e.name == 'Priority'
|
33
|
+
@priority = e.text.to_i
|
34
|
+
elsif e.name == 'ReceiptHandle'
|
35
|
+
@receipt_handle = e.text
|
36
|
+
elsif e.name == 'NextVisibleTime'
|
37
|
+
@next_visible_at = e.text.to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# verify body
|
43
|
+
md5 = Digest::MD5.hexdigest(body).upcase
|
44
|
+
unless md5 == body_md5
|
45
|
+
raise Exception,
|
46
|
+
'Invalid MNS Body, MD5 does not match, '\
|
47
|
+
"MD5 #{body_md5}, expect MD5 #{md5}, Body: #{body}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'uri'
|
5
|
+
require 'rest-client'
|
6
|
+
require 'rexml/document'
|
7
|
+
|
8
|
+
module LogStash
|
9
|
+
module Inputs
|
10
|
+
module MNS
|
11
|
+
# Class for Aliyun MNS Request.
|
12
|
+
class Request
|
13
|
+
include REXML
|
14
|
+
|
15
|
+
attr_reader :log, :uri, :method, :body, :content_md5, :content_type,
|
16
|
+
:content_length, :mns_headers, :access_key_id,
|
17
|
+
:access_key_secret, :endpoint
|
18
|
+
|
19
|
+
def initialize(opts, headers, params)
|
20
|
+
@log = opts[:log]
|
21
|
+
conf = {
|
22
|
+
host: opts[:endpoint],
|
23
|
+
path: opts[:path]
|
24
|
+
}
|
25
|
+
|
26
|
+
conf[:query] = URI.encode_www_form(params) unless params.empty?
|
27
|
+
@uri = URI::HTTP.build(conf)
|
28
|
+
@method = opts[:method].to_s.downcase
|
29
|
+
@mns_headers = headers.merge('x-mns-version' => '2015-06-06')
|
30
|
+
@access_key_id = opts[:access_key_id]
|
31
|
+
@access_key_secret = opts[:access_key_secret]
|
32
|
+
|
33
|
+
log.info uri.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def content(type, values = {})
|
37
|
+
ns = 'http://mns.aliyuncs.com/doc/v1/'
|
38
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
39
|
+
xml.send(type.to_sym, xmlns: ns) do |b|
|
40
|
+
values.each { |k, v| b.send k.to_sym, v }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@body = builder.to_xml
|
44
|
+
@content_md5 = Base64.encode64(Digest::MD5.hexdigest(body)).chop
|
45
|
+
@content_length = body.size
|
46
|
+
@content_type = 'text/xml;charset=utf-8'
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute
|
50
|
+
date = DateTime.now.httpdate
|
51
|
+
headers = {
|
52
|
+
'Authorization' => authorization(date),
|
53
|
+
'Content-Length' => content_length || 0,
|
54
|
+
'Content-Type' => content_type,
|
55
|
+
'Content-MD5' => content_md5,
|
56
|
+
'Date' => date,
|
57
|
+
'Host' => uri.host
|
58
|
+
}.merge(@mns_headers).reject { |k, v| v.nil? }
|
59
|
+
|
60
|
+
begin
|
61
|
+
RestClient.send *[method, uri.to_s, headers, body].compact
|
62
|
+
rescue RestClient::Exception => e
|
63
|
+
doc = Document.new(e.response.to_s)
|
64
|
+
doc.elements[1].each do |e|
|
65
|
+
next unless e.node_type == :element
|
66
|
+
return nil if (e.name == 'Code') && (e.text == 'MessageNotExist')
|
67
|
+
end
|
68
|
+
|
69
|
+
log.error e.response
|
70
|
+
|
71
|
+
raise e
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def authorization(date)
|
76
|
+
canonical_resource = [uri.path, uri.query].compact.join('?')
|
77
|
+
canonical_headers = mns_headers.sort.collect { |k, v| "#{k.downcase}:#{v}" }.join("\n")
|
78
|
+
signature = [method.to_s.upcase, content_md5 || '', content_type || '', date, canonical_headers, canonical_resource].join("\n")
|
79
|
+
sha1 = OpenSSL::HMAC.digest('sha1', access_key_secret, signature)
|
80
|
+
"MNS #{access_key_id}:#{Base64.encode64(sha1).chop}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'json'
|
3
|
+
require 'logstash/inputs/base'
|
4
|
+
require 'logstash-input-oss_jars'
|
5
|
+
|
6
|
+
java_import com.aliyun.oss.OSS
|
7
|
+
java_import com.aliyun.oss.OSSClientBuilder
|
8
|
+
java_import com.aliyun.oss.ClientBuilderConfiguration
|
9
|
+
java_import com.aliyun.oss.model.GetObjectRequest
|
10
|
+
java_import java.io.InputStream
|
11
|
+
java_import java.io.InputStreamReader
|
12
|
+
java_import java.io.FileInputStream
|
13
|
+
java_import java.io.BufferedReader
|
14
|
+
java_import java.util.zip.GZIPInputStream
|
15
|
+
java_import java.util.zip.ZipException
|
16
|
+
|
17
|
+
#
|
18
|
+
# LogStash OSS Input Plugin
|
19
|
+
# Stream events from files from an OSS bucket.
|
20
|
+
# Each line from each file generates an event.
|
21
|
+
# Files end with `.gz` or `.gzip` are handled as gzip'ed files.
|
22
|
+
#
|
23
|
+
class LogStash::Inputs::OSS < LogStash::Inputs::Base
|
24
|
+
require 'logstash/inputs/mns/message'
|
25
|
+
require 'logstash/inputs/mns/request'
|
26
|
+
require 'logstash/inputs/version'
|
27
|
+
|
28
|
+
MAX_CONNECTIONS_TO_OSS_KEY = "max_connections_to_oss"
|
29
|
+
|
30
|
+
SECURE_CONNECTION_ENABLED_KEY = "secure_connection_enabled"
|
31
|
+
|
32
|
+
MNS_ENDPOINT = "endpoint"
|
33
|
+
|
34
|
+
MNS_QUEUE = "queue"
|
35
|
+
|
36
|
+
MNS_WAIT_SECONDS = "wait_seconds"
|
37
|
+
|
38
|
+
MNS_POLL_INTERVAL_SECONDS = "poll_interval_seconds"
|
39
|
+
|
40
|
+
config_name "oss"
|
41
|
+
|
42
|
+
default :codec, "plain"
|
43
|
+
|
44
|
+
# The name of OSS bucket.
|
45
|
+
config :bucket, :validate => :string, :required => true
|
46
|
+
|
47
|
+
# OSS endpoint to connect
|
48
|
+
config :endpoint, :validate => :string, :required => true
|
49
|
+
|
50
|
+
# access key id
|
51
|
+
config :access_key_id, :validate => :string, :required => true
|
52
|
+
|
53
|
+
# access secret key
|
54
|
+
config :access_key_secret, :validate => :string, :required => true
|
55
|
+
|
56
|
+
# If specified, the prefix of filenames in the bucket must match (not a regexp)
|
57
|
+
config :prefix, :validate => :string, :default => nil
|
58
|
+
|
59
|
+
# additional oss client configurations, valid keys are:
|
60
|
+
# secure_connection_enabled(enable https or not)
|
61
|
+
# max_connections_to_oss(max connections to oss)
|
62
|
+
# TODO: add other oss configurations
|
63
|
+
config :additional_oss_settings, :validate => :hash, :default => nil
|
64
|
+
|
65
|
+
# Name of an OSS bucket to backup processed files to.
|
66
|
+
config :backup_to_bucket, :validate => :string, :default => nil
|
67
|
+
|
68
|
+
# Append a prefix to the key (full path including file name in OSS) after processing.
|
69
|
+
# If backing up to another (or the same) bucket, this effectively lets you
|
70
|
+
# choose a new 'folder' to place the files in
|
71
|
+
config :backup_add_prefix, :validate => :string, :default => nil
|
72
|
+
|
73
|
+
# Path of a local directory to backup processed files to.
|
74
|
+
config :backup_to_dir, :validate => :string, :default => nil
|
75
|
+
|
76
|
+
# Whether to delete processed files from the original bucket.
|
77
|
+
config :delete, :validate => :boolean, :default => false
|
78
|
+
|
79
|
+
# MNS configurations, valid keys are:
|
80
|
+
# endpoint: MNS endpoint to connect to
|
81
|
+
# queue: MNS queue to poll messages
|
82
|
+
# wait_seconds: MNS max waiting time to receive messages, not required
|
83
|
+
# poll_interval_seconds: Poll messages interval from MNS if there is no message this time, default 10 seconds
|
84
|
+
config :mns_settings, :validate => :hash, :required => true
|
85
|
+
|
86
|
+
# Ruby style regexp of keys to exclude from the bucket
|
87
|
+
config :exclude_pattern, :validate => :string, :default => nil
|
88
|
+
|
89
|
+
# Whether or not to include the OSS object's properties (last_modified, content_type, metadata)
|
90
|
+
# into each Event at [@metadata][oss]. Regardless of this setting, [@metadata][oss][key] will always
|
91
|
+
# be present.
|
92
|
+
config :include_object_properties, :validate => :boolean, :default => false
|
93
|
+
|
94
|
+
# For testing
|
95
|
+
config :stop_for_test, :validate => :boolean, :default => false
|
96
|
+
|
97
|
+
public
|
98
|
+
def register
|
99
|
+
@logger.info("Registering oss input", :bucket => @bucket)
|
100
|
+
|
101
|
+
# initialize oss client
|
102
|
+
@oss = initialize_oss_client
|
103
|
+
|
104
|
+
unless @backup_to_bucket.nil?
|
105
|
+
if @backup_to_bucket == @bucket
|
106
|
+
raise LogStash::ConfigurationError, "Logstash Input OSS Plugin: backup bucket and source bucket should be different"
|
107
|
+
end
|
108
|
+
|
109
|
+
unless @oss.doesBucketExist(@backup_to_bucket)
|
110
|
+
@oss.createBucket(@backup_to_bucket)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
unless @backup_to_dir.nil?
|
115
|
+
Dir.mkdir(@backup_to_dir, 0700) unless File.exists?(@backup_to_dir)
|
116
|
+
end
|
117
|
+
|
118
|
+
if @mns_settings.include?(MNS_POLL_INTERVAL_SECONDS)
|
119
|
+
@interval = @mns_settings[MNS_POLL_INTERVAL_SECONDS]
|
120
|
+
else
|
121
|
+
@interval = 10
|
122
|
+
end
|
123
|
+
|
124
|
+
@mns_endpoint = @mns_settings[MNS_ENDPOINT]
|
125
|
+
@mns_queue = @mns_settings[MNS_QUEUE]
|
126
|
+
@mns_wait_seconds = @mns_settings[MNS_WAIT_SECONDS]
|
127
|
+
end
|
128
|
+
|
129
|
+
public
|
130
|
+
def run(queue)
|
131
|
+
@current_thread = Thread.current
|
132
|
+
Stud.interval(0.01) do
|
133
|
+
@logger.info "Start to poll message from MNS queue #{@mns_queue}"
|
134
|
+
process_objects(queue)
|
135
|
+
stop if stop?
|
136
|
+
if @stop_for_test and not @fetched
|
137
|
+
do_stop
|
138
|
+
end
|
139
|
+
sleep @interval unless @fetched
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
public
|
144
|
+
def stop
|
145
|
+
# @current_thread is initialized in the `#run` method,
|
146
|
+
# this variable is needed because the `#stop` is a called in another thread
|
147
|
+
# than the `#run` method and requiring us to call stop! with a explicit thread.
|
148
|
+
@logger.info("Logstash OSS Input Plugin is shutting down...")
|
149
|
+
Stud.stop!(@current_thread)
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def process_objects(queue)
|
154
|
+
message = receive_message
|
155
|
+
if message.nil?
|
156
|
+
@fetched = false
|
157
|
+
else
|
158
|
+
@fetched = true
|
159
|
+
process(LogStash::Inputs::MNS::Message.new(@mns_queue, message), queue)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def receive_message
|
165
|
+
request_opts = {}
|
166
|
+
request_opts = { waitseconds: @mns_wait_seconds } if @mns_wait_seconds
|
167
|
+
opts = {
|
168
|
+
log: @logger,
|
169
|
+
method: 'GET',
|
170
|
+
endpoint: @mns_endpoint,
|
171
|
+
path: "/queues/#{@mns_queue}/messages",
|
172
|
+
access_key_id: @access_key_id,
|
173
|
+
access_key_secret: @access_key_secret
|
174
|
+
}
|
175
|
+
LogStash::Inputs::MNS::Request.new(opts, {}, request_opts).execute
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def process(message, queue)
|
180
|
+
objects = get_objects(message)
|
181
|
+
objects.each do |object|
|
182
|
+
key = object.key
|
183
|
+
if key.end_with?("/")
|
184
|
+
@logger.info("Skip directory " + key)
|
185
|
+
elsif @prefix and @prefix != "" and not key.start_with?(@prefix)
|
186
|
+
@logger.info("Skip object " + key + " because object name does not match " + @prefix)
|
187
|
+
elsif @exclude_pattern and key =~ Regexp.new(@exclude_pattern)
|
188
|
+
@logger.info("Skip object " + key + " because object name matches exclude_pattern")
|
189
|
+
else
|
190
|
+
metadata = {}
|
191
|
+
begin
|
192
|
+
read_object(key) do |line, object_meta|
|
193
|
+
@codec.decode(line) do |event|
|
194
|
+
if event_is_metadata?(event)
|
195
|
+
@logger.debug('Event is metadata, updating the current cloudfront metadata', :event => event)
|
196
|
+
update_metadata(metadata, event)
|
197
|
+
else
|
198
|
+
decorate(event)
|
199
|
+
|
200
|
+
event.set("cloudfront_version", metadata[:cloudfront_version]) unless metadata[:cloudfront_version].nil?
|
201
|
+
event.set("cloudfront_fields", metadata[:cloudfront_fields]) unless metadata[:cloudfront_fields].nil?
|
202
|
+
|
203
|
+
if @include_object_properties
|
204
|
+
object_meta.each do |key, value|
|
205
|
+
event.set("[@metadata][oss][" + key + "]", value.to_s)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
event.set("[@metadata][oss]", {})
|
209
|
+
end
|
210
|
+
|
211
|
+
event.set("[@metadata][oss][key]", key)
|
212
|
+
queue << event
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
@codec.flush do |event|
|
217
|
+
queue << event
|
218
|
+
end
|
219
|
+
end
|
220
|
+
backup_to_bucket(key)
|
221
|
+
backup_to_dir(key)
|
222
|
+
delete_file_from_bucket(key)
|
223
|
+
rescue Exception => e
|
224
|
+
# skip any broken file
|
225
|
+
@logger.error("Failed to read object. Skip processing.", :key => key, :exception => e.message)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
delete_message(message)
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
def event_is_metadata?(event)
|
234
|
+
return false unless event.get("message").class == String
|
235
|
+
line = event.get("message")
|
236
|
+
version_metadata?(line) || fields_metadata?(line)
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
def version_metadata?(line)
|
241
|
+
line.start_with?('#Version: ')
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
def fields_metadata?(line)
|
246
|
+
line.start_with?('#Fields: ')
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
def update_metadata(metadata, event)
|
251
|
+
line = event.get('message').strip
|
252
|
+
|
253
|
+
if version_metadata?(line)
|
254
|
+
metadata[:cloudfront_version] = line.split(/#Version: (.+)/).last
|
255
|
+
end
|
256
|
+
|
257
|
+
if fields_metadata?(line)
|
258
|
+
metadata[:cloudfront_fields] = line.split(/#Fields: (.+)/).last
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
def read_object(key, &block)
|
264
|
+
@logger.info("Processing object " + key)
|
265
|
+
object = @oss.getObject(@bucket, key)
|
266
|
+
meta = object.getObjectMetadata.getRawMetadata
|
267
|
+
input = object.getObjectContent
|
268
|
+
content = input
|
269
|
+
if gzip?(key)
|
270
|
+
content = GZIPInputStream.new(input)
|
271
|
+
end
|
272
|
+
|
273
|
+
decoder = InputStreamReader.new(content, "UTF-8")
|
274
|
+
buffered = BufferedReader.new(decoder)
|
275
|
+
|
276
|
+
while (line = buffered.readLine)
|
277
|
+
block.call(line, meta)
|
278
|
+
end
|
279
|
+
ensure
|
280
|
+
buffered.close unless buffered.nil?
|
281
|
+
decoder.close unless decoder.nil?
|
282
|
+
content.close unless content.nil?
|
283
|
+
input.close unless input.nil?
|
284
|
+
end
|
285
|
+
|
286
|
+
private
|
287
|
+
def gzip?(filename)
|
288
|
+
filename.end_with?('.gz','.gzip')
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
def backup_to_bucket(key)
|
293
|
+
unless @backup_to_bucket.nil?
|
294
|
+
backup_key = "#{@backup_add_prefix}#{key}"
|
295
|
+
@oss.copyObject(@bucket, key, @backup_to_bucket, backup_key)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
private
|
300
|
+
def backup_to_dir(key)
|
301
|
+
unless @backup_to_dir.nil?
|
302
|
+
file = @backup_to_dir + '/' + key
|
303
|
+
|
304
|
+
dirname = File.dirname(file)
|
305
|
+
unless File.directory?(dirname)
|
306
|
+
FileUtils.mkdir_p(dirname)
|
307
|
+
end
|
308
|
+
|
309
|
+
@oss.getObject(GetObjectRequest.new(@bucket, key), java.io.File.new(file))
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
def delete_file_from_bucket(key)
|
315
|
+
if @delete
|
316
|
+
@oss.deleteObject(@bucket, key)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
private
|
321
|
+
def get_objects(message)
|
322
|
+
objects = []
|
323
|
+
events = JSON.parse(Base64.decode64(message.body))['events']
|
324
|
+
events.each do |event|
|
325
|
+
objects.push(OSSObject.new(event['eventName'],
|
326
|
+
@bucket,
|
327
|
+
event['oss']['object']['key'],
|
328
|
+
event['oss']['object']['size'],
|
329
|
+
event['oss']['object']['eTag']))
|
330
|
+
end
|
331
|
+
objects
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
def delete_message(message)
|
336
|
+
request_opts = { ReceiptHandle: message.receipt_handle }
|
337
|
+
opts = {
|
338
|
+
log: @logger,
|
339
|
+
method: 'DELETE',
|
340
|
+
endpoint: @mns_endpoint,
|
341
|
+
path: "/queues/#{@mns_queue}/messages",
|
342
|
+
access_key_id: @access_key_id,
|
343
|
+
access_key_secret: @access_key_secret
|
344
|
+
}
|
345
|
+
LogStash::Inputs::MNS::Request.new(opts, {}, request_opts).execute
|
346
|
+
end
|
347
|
+
|
348
|
+
# OSS Object class from MNS events
|
349
|
+
class OSSObject
|
350
|
+
attr_reader :event_name, :bucket, :key, :size, :etag
|
351
|
+
def initialize(event_name, bucket, key, size, etag)
|
352
|
+
@event_name = event_name
|
353
|
+
@bucket = bucket
|
354
|
+
@key = key
|
355
|
+
@size = size
|
356
|
+
@etag = etag
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
private
|
361
|
+
def initialize_oss_client
|
362
|
+
clientConf = ClientBuilderConfiguration.new
|
363
|
+
unless @additional_oss_settings.nil?
|
364
|
+
if @additional_oss_settings.include?(SECURE_CONNECTION_ENABLED_KEY)
|
365
|
+
clientConf.setProtocol(@additional_oss_settings[SECURE_CONNECTION_ENABLED_KEY] ?
|
366
|
+
com.aliyun.oss.common.comm.Protocol::HTTPS : com.aliyun.oss.common.comm.Protocol::HTTP)
|
367
|
+
end
|
368
|
+
|
369
|
+
if @additional_oss_settings.include?(MAX_CONNECTIONS_TO_OSS_KEY)
|
370
|
+
if @additional_oss_settings[MAX_CONNECTIONS_TO_OSS_KEY] <= 0
|
371
|
+
raise LogStash::ConfigurationError, "Logstash OSS Input plugin must have positive " + MAX_CONNECTIONS_TO_OSS_KEY
|
372
|
+
end
|
373
|
+
clientConf.setMaxConnections(@additional_oss_settings[MAX_CONNECTIONS_TO_OSS_KEY])
|
374
|
+
else
|
375
|
+
clientConf.setMaxConnections(1024)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
clientConf.setUserAgent(clientConf.getUserAgent() + ", Logstash-oss/" + Version.version)
|
380
|
+
OSSClientBuilder.new().build(@endpoint, @access_key_id, @access_key_secret, clientConf)
|
381
|
+
end
|
382
|
+
end
|