logstash-input-oss 0.0.1-java
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 +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
|