logstash-output-qingstor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff8942eb9e9d52efdd58c06bfdeb77abc8698d2c
4
+ data.tar.gz: 34704f434b5c411eb7ab5bd3cbf4a553b9e41176
5
+ SHA512:
6
+ metadata.gz: 80a48c0492352fe8d1b486eccb9e4dc3848bb3aafefc8cc0229a0db3ffb63f7346cce08e8dc4efad7fcfa09f9c3a78681f2d0b81d8e705afc8d1f902068ede28
7
+ data.tar.gz: cc45d155ba152691d7972e4895f838ea4f3486fccf59bea0dd60480bbb1c6d154b2d5d3a84b356a741c1632d8fb02553c338fa7dd5e38c5966cf2888a7508b3e
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0
2
+ - Plugin created with the logstash plugin generator
@@ -0,0 +1,44 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to creating a positive environment
10
+ include:
11
+
12
+ * Using welcoming and inclusive language
13
+ * Being respectful of differing viewpoints and experiences
14
+ * Gracefully accepting constructive criticism
15
+ * Focusing on what is best for the community
16
+ * Showing empathy towards other community members
17
+
18
+ Examples of unacceptable behavior by participants include:
19
+
20
+ * The use of sexualized language or imagery and unwelcome sexual attention or advances
21
+ * Trolling, insulting/derogatory comments, and personal or political attacks
22
+ * Public or private harassment
23
+ * Publishing others' private information, such as a physical or electronic address, without explicit permission
24
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
25
+
26
+ ## Our Responsibilities
27
+
28
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
29
+
30
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
31
+
32
+ ## Scope
33
+
34
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
35
+
36
+ ## Enforcement
37
+
38
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pengsrc@yunify.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
39
+
40
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
41
+
42
+ ## Attribution
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][http://contributor-covenant.org], version 1.4, available at [http://contributor-covenant.org/version/1/4][http://contributor-covenant.org/version/1/4].
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,43 @@
1
+ # Contributing Guidelines
2
+
3
+ We love contributions from everyone. By participating in this project, you agree to abide by the [_`Contributor Covenant Code of Conduct`_](./CODE_OF_CONDUCT.md) and the conventions described in this documentation.
4
+
5
+ ## Getting Started
6
+
7
+ ### Reporting Issues
8
+
9
+ A great way to contribute to the project is to send a detailed report when you encounter an issue. We always appreciate a well-written, thorough bug report, and will thank you for it!
10
+
11
+ Check that [our issues list](https://github.com/Tacinight/logstash-output-qingstor/issues) doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates. Do not leave random "+1" or "I have this too" comments, as they only clutter the discussion, and don't help resolving it. However, if you have ways to reproduce the issue or have additional information that may help resolving the issue, please leave a comment.
12
+
13
+ When reporting issues, please describe what happened clearly, and include the steps required to reproduce the problem if possible and applicable. This information will help us review and fix your issue faster. When sending lengthy log-files, consider posting them as a gist (https://gist.github.com). Don't forget to remove sensitive data from your log files before posting (you can replace those parts with "CONFIDENTIAL").
14
+
15
+ ### Sending Pull Request
16
+
17
+ Pull requests are always welcome, we will appreciate it.
18
+
19
+ Any significant improvement should be documented as a GitHub [issue](https://github.com/Tacinight/logstash-output-qingstor/issues) before anybody starts working on it.
20
+
21
+ 1. Fork it ( https://github.com/Tacinight/logstash-output-qingstor/fork )
22
+ 2. Create your feature branch (`git checkout -b new-feature`)
23
+ 3. Commit your changes (`git commit -asm 'Add some feature'`)
24
+ 4. Push to the branch (`git push origin new-feature`)
25
+ 5. Create a new Pull Request
26
+
27
+ ## Conventions
28
+
29
+ ### Patch Content
30
+
31
+ * Always remove the trailing whitespace
32
+ * Keep a newline at the end of text files
33
+ * Exclude irrelevant files, such as `*.a`, `.bundle`, and `.DS_Store`
34
+ * Always pass all the tests before submitting
35
+ * Let the propose of each patch simple and clear
36
+
37
+ ### Commit Message
38
+
39
+ * Capitalize the subject line
40
+ * Limit the subject line to 50 characters
41
+ * Do not end the subject line with a period
42
+ * Wrap the body at 72 characters
43
+ * Use the body to explain what happened
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
+ * Evan Zhao - tacingiht@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,2 @@
1
+ # logstash-output-qinstor
2
+ Example output plugin. This should help bootstrap your effort to write your own output plugin!
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
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,181 @@
1
+ # Logstash Output Plugin for Qingstor
2
+
3
+ 这是一个适配了[Qingstor](https://www.qingcloud.com/products/storage#qingstor), 工作在logstash中的output插件.
4
+ Qingstor是[Qingcloud](https://www.qingcloud.com/)推出的对象存储服务.
5
+ 作为一个output插件, 可以将logstash输出的结果打包上传至Qingstor中.
6
+ 详细功能参考下面配置说明.
7
+
8
+ 目前插件已经提交至rubygems.org, 使用以下命令安装:
9
+ ```sh
10
+ bin/logstash-plugin install logstash-output-qingstor
11
+ ```
12
+ 手动安装本地代码, 安装方法参考下文.
13
+
14
+ ## 1. 配置说明
15
+
16
+ #### 1.1 最小运行配置
17
+ - 使用'-f'接受一个*.conf文件或者使用'-e'参数时, 最小运行配置至少需要以下三项
18
+ ```sh
19
+ output {
20
+ qingstor {
21
+ access_key_id => 'your_access_key_id' #required
22
+ secret_access_key => 'your_secret_access_key' #required
23
+ bucket => 'bucket_name' #required
24
+ # region => "pek3a" #optional, default value "pek3a"
25
+ }
26
+ }
27
+
28
+ ```
29
+
30
+ #### 1.2 其他可选参数说明
31
+ ```sh
32
+ output {
33
+ qingstor {
34
+ ......
35
+ # 前缀, 生成本地子目录, 并且添加到上传文件名的前半部分, 形成Qingstor的目录.
36
+ # 默认空,
37
+ prefix => 'aprefix'
38
+
39
+ # 本地保存临时文件的目录.
40
+ # 默认: 系统临时文件目录下的logstash2qingstor文件夹, 例如linux下 "/tmp/logstash2qingstor"
41
+ tmpdir => '/local/temporary/directory'
42
+
43
+ # 字符串数组, 添加到文件名中, 例如["a", "b", "c"], 文件名会形成如 xxx-a-b-c-xxx.xx
44
+ # 默认: 空
45
+ tags => ["tag1", "tag2", "tag3"]
46
+
47
+ # 上传文件的格式. 可选"gzip", 后缀为".gz".
48
+ # 默认: "none", 后缀".log".
49
+ encoding => "gzip"
50
+
51
+ # 文件上传的策略.
52
+ # 分别表示结合文件大小和时间的综合策略, 基于文件大小的策略, 以及基于时间的策略.
53
+ # 基于文件大小的策略表示: 当文件大小满足预设值之后, 将文件上传.
54
+ # 基于时间的策略表示: 每经过预设时间之后, 将文件上传.
55
+ # 默认: "size_and_time". 可选枚举值["size_and_time", "size", "time"].
56
+ rotation_strategy => "size_and_time"
57
+
58
+ # 配合"size_and_time", "size"的可选配置型, 单位byte
59
+ # 默认: 1024*1024*5 (byte) = 5 (MB)
60
+ size_file => 1024*1024*5
61
+
62
+ # 配合"size_and_time", "time"的可选配置型, 单位minute
63
+ # 默认: 15 (minutes)
64
+ time_file => 15
65
+
66
+ # 服务端文件加密, 支持AES256.
67
+ # 默认: "none". 可选枚举值: ["AES256", "none"]
68
+ server_side_encryption_algorithm => "AES256"
69
+
70
+ # 选用服务端文件加密时提供的秘钥, 秘钥要求32位/256bit
71
+ customer_key => "your_encryption_key"
72
+
73
+ # 宕机恢复, 启动logstash时, 自动上传目录下的遗留文件
74
+ # 默认: false
75
+ restore => true
76
+
77
+ }
78
+ }
79
+
80
+ ```
81
+
82
+ ## 2. 安装插件
83
+
84
+ #### 2.1 直接运行本地的插件
85
+
86
+ - 编辑Logstash目录下的Genfile, 添加插件的路径, 例如
87
+ ```ruby
88
+ gem "logstash-output-qingstor", :path => "/your/local/logstash-output-qingstor"
89
+ ```
90
+ - 安装插件
91
+ ```sh
92
+ bin/logstash-plugin install --no-verify
93
+ ```
94
+ - 使用插件运行
95
+ ```sh
96
+ bin/logstash -e "output {qingstor {access_key_id => 'your_access_key_id'
97
+ secret_access_key => 'your_secret_access_key'
98
+ bucket => 'bucket_name' }}"
99
+ ```
100
+ 此时你对插件所做的任意的代码上的修改都会直接生效.
101
+
102
+ #### 2.2 安装一个本地插件然后运行
103
+
104
+ 这一步你需要生成一个插件的gem包, 然后通过logstash来安装到logstash的插件目录下
105
+ - 在项目目录下生成gem
106
+ ```sh
107
+ gem build logstash-output-qingstor.gemspec
108
+ ```
109
+ - 在Logstash的目录下使用logstash-plugin安装
110
+ ```sh
111
+ bin/logstash-plugin install /your/local/plugin/logstash-output-qingstor.gem
112
+ ```
113
+ - 安装完毕之后, 就可以使用Logstash运行开始测试了.
114
+
115
+
116
+ # Logstash Output Plugin for Qingstor
117
+
118
+ As an output plugin, it can collect the outputs from logstash, and store them in [Qingstor](https://www.qingcloud.com/products/storage#qingstor), which is a remarkable object storage service provided by [Qingcloud](https://www.qingcloud.com/).
119
+
120
+ For now, We've submitted this plugin to rubygems.org. Use the following command to install.
121
+ ```sh
122
+ bin/logstash-plugin install logstash-output-qingstor
123
+ ```
124
+ If want to install the local code, please refer to the following guide to install it manually.
125
+
126
+ ## 1. Configuration Guide
127
+
128
+ #### 1.1 Run in minimal Configuration Items
129
+ ```sh
130
+ output {
131
+ qingstor {
132
+ access_key_id => 'your_access_key_id' #required
133
+ secret_access_key => 'your_secret_access_key' #required
134
+ bucket => 'bucket_name' #required
135
+ # region => "pek3a" #optional, default value "pek3a"
136
+ }
137
+ }
138
+
139
+ ```
140
+
141
+ More configuration details please refer to [code part](lib/logstash/outputs/qingstor.rb).
142
+
143
+ ## 2. Running your unpublished Plugin in Logstash
144
+
145
+ #### 2.1 Run in a local Logstash clone
146
+
147
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
148
+ ```ruby
149
+ gem "logstash-output-qingstor", :path => "/your/local/logstash-output-qingstor"
150
+ ```
151
+ - Install plugin
152
+ ```sh
153
+ bin/logstash-plugin install --no-verify
154
+ ```
155
+ - Run Logstash with your plugin
156
+ ```sh
157
+ bin/logstash -e "output {qingstor {access_key_id => 'your_access_key_id'
158
+ secret_access_key => 'your_secret_access_key'
159
+ bucket => 'bucket_name' }}"
160
+ ```
161
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
162
+
163
+ #### 2.2 Run in an installed Logstash
164
+
165
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
166
+
167
+ - Build your plugin gem
168
+ ```sh
169
+ gem build logstash-output-qingstor.gemspec
170
+ ```
171
+ - Install the plugin from the Logstash home
172
+ ```sh
173
+ bin/logstash-plugin install /your/local/plugin/logstash-output-qingstor.gem
174
+ ```
175
+ - Start Logstash and proceed to test the plugin
176
+
177
+ ## Contributing
178
+ Please see [Contributing Guidelines](./CONTRIBUTING.md) of this project before submitting patches.
179
+
180
+ ## LICENSE
181
+ The Apache License (Version 2.0, January 2004).
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+ require "java"
3
+ require "concurrent"
4
+ require "concurrent/timer_task"
5
+ require "logstash/util"
6
+
7
+ ConcurrentHashMap = java.util.concurrent.ConcurrentHashMap
8
+
9
+ module LogStash
10
+ module Outputs
11
+ class Qingstor
12
+ class FileRepository
13
+ DEFAULT_STATE_SWEEPER_INTERVAL_SECS = 60
14
+ DEFAULT_STATE_TIME_SECS = 15 * 60
15
+ class PrefixedValue
16
+ def initialize(file_factory, stale_time)
17
+ @file_factory = file_factory
18
+ @lock = Mutex.new
19
+ @stale_time = stale_time
20
+ end
21
+
22
+ def with_lock
23
+ @lock.synchronize {
24
+ yield @file_factory
25
+ }
26
+ end
27
+
28
+ def stale?
29
+ with_lock { |factory| factory.current.size == 0 && (Time.now - factory.current.ctime > @stale_time) }
30
+ end
31
+
32
+ def apply(prefix)
33
+ return self
34
+ end
35
+
36
+ def delete!
37
+ with_lock { |factory| factory.current.delete! }
38
+ end
39
+ end
40
+
41
+ class FactoryInitializer
42
+ def initialize(tags, encoding, temporary_directory, stale_time)
43
+ @tags = tags
44
+ @encoding = encoding
45
+ @temporary_directory = temporary_directory
46
+ @stale_time = stale_time
47
+ end
48
+
49
+ def apply(prefix_key)
50
+ PrefixedValue.new(TemporaryFileFactory.new(prefix_key, @tags, @encoding, @temporary_directory), @stale_time)
51
+ end
52
+ end
53
+
54
+ def initialize(tags, encoding, temporary_directory,
55
+ stale_time = DEFAULT_STATE_TIME_SECS,
56
+ sweeper_interval = DEFAULT_STATE_SWEEPER_INTERVAL_SECS)
57
+ @prefixed_factories = ConcurrentHashMap.new
58
+ @sweeper_interval = sweeper_interval
59
+ @factoryinitializer = FactoryInitializer.new(tags, encoding, temporary_directory, stale_time)
60
+
61
+ start_stale_sweeper
62
+ end
63
+
64
+ def keys
65
+ @prefixed_factories.keySet
66
+ end
67
+
68
+ def each_files
69
+ @prefixed_factories.elements.each do |prefixed_file|
70
+ prefixed_file.with_lock { |factory| yield factory.current }
71
+ end
72
+ end
73
+
74
+ def get_factory(prefix_key)
75
+ @prefixed_factories.computeIfAbsent(prefix_key, @factoryinitializer).with_lock { |factory| yield factory }
76
+ end
77
+
78
+ def get_file(prefix_key)
79
+ get_factory(prefix_key) { |factory| yield factory.current }
80
+ end
81
+
82
+ def shutdown
83
+ stop_stale_sweeper
84
+ end
85
+
86
+ def size
87
+ @prefixed_factories.size
88
+ end
89
+
90
+ def remove_stale(k, v)
91
+ if v.stale?
92
+ @prefixed_factories.remove(k, v)
93
+ v.delete!
94
+ end
95
+ end
96
+
97
+ def start_stale_sweeper
98
+ @stale_sweeper = Concurrent::TimerTask.new(:execution_interval => @sweeper_interval) do
99
+ LogStash::Util.set_thread_name("Qingstor, stale factory sweeper")
100
+ @prefixed_factories.forEach { |k, v| remove_stale(k, v) }
101
+ end
102
+
103
+ @stale_sweeper.execute
104
+ end
105
+
106
+ def stop_stale_sweeper
107
+ @stale_sweeper.shutdown
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require "qingstor/sdk"
3
+ require "fileutils"
4
+
5
+ module LogStash
6
+ module Outputs
7
+ class Qingstor
8
+ class QingstorValidator
9
+
10
+ def self.bucket_valid?(bucket)
11
+ res = bucket.head
12
+ case res[:status_code]
13
+ when 401
14
+ raise LogStash::ConfigurationError, "Incorrect key id or access key."
15
+ when 404
16
+ raise LogStash::ConfigurationError, "Incorrect bucket/region name."
17
+ end
18
+ true
19
+ end
20
+
21
+ def self.prefix_valid?(prefix)
22
+ if prefix.start_with?("/") || prefix.length >= 1024
23
+ raise LogStash::ConfigurationError, "Prefix must not start with '/' with length less than 1024 "
24
+ end
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/qingstor/size_rotation_policy"
3
+ require "logstash/outputs/qingstor/time_rotation_policy"
4
+
5
+ module LogStash
6
+ module Outputs
7
+ class Qingstor
8
+ class SizeAndTimeRotationPolicy
9
+ def initialize(size_file, time_file)
10
+ @size_strategy = SizeRotationPolicy.new(size_file)
11
+ @time_strategy = TimeRotationPolicy.new(time_file)
12
+ end
13
+
14
+ def rotate?(file)
15
+ @size_strategy.rotate?(file) || @time_strategy.rotate?(file)
16
+ end
17
+
18
+ def needs_periodic?
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module LogStash
3
+ module Outputs
4
+ class Qingstor
5
+ class SizeRotationPolicy
6
+ attr_reader :size_file
7
+
8
+ def initialize(size_file)
9
+ if size_file <= 0
10
+ raise LogStash::ConfigurationError, "'size_file' need to be greater than 0"
11
+ end
12
+
13
+ @size_file = size_file
14
+ end
15
+
16
+ def rotate?(file)
17
+ file.size >= size_file
18
+ end
19
+
20
+ def needs_periodic?
21
+ false
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+ require "thread"
3
+ require "forwardable"
4
+ require "fileutils"
5
+ require "pathname"
6
+
7
+ module LogStash
8
+ module Outputs
9
+ class Qingstor
10
+ class TemporaryFile
11
+ extend Forwardable
12
+
13
+ def_delegators :@fd, :path, :write, :close, :fsync
14
+
15
+ attr_reader :fd
16
+
17
+ def initialize(key, fd, tmp_path)
18
+ @key = key
19
+ @fd = fd
20
+ @tmp_path = tmp_path
21
+ @created_at = Time.now
22
+ end
23
+
24
+ def ctime
25
+ @created_at
26
+ end
27
+
28
+ def tmp_path
29
+ @tmp_path
30
+ end
31
+
32
+ def size
33
+ begin
34
+ @fd.size
35
+ rescue IOError
36
+ ::File.size(path)
37
+ end
38
+ end
39
+
40
+ def key
41
+ @key.gsub(/^\//, "")
42
+ end
43
+
44
+ def delete!
45
+ @fd.close rescue IOError
46
+ FileUtils.rm_r(@tmp_path, :secure => true)
47
+ end
48
+
49
+ def empty?
50
+ size == 0
51
+ end
52
+
53
+ def self.create_from_existing_file(file_path, tmp_folder)
54
+ key_parts = Pathname.new(file_path).relative_path_from(tmp_folder).to_s.split(::File::SEPARATOR)
55
+ TemporaryFile.new(key_parts.slice(1, key_parts.size).join("/"),
56
+ ::File.open(file_path, "r"),
57
+ ::File.join(tmp_folder, key_parts.slice(0, 1)))
58
+
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+ require "socket"
3
+ require "securerandom"
4
+ require "fileutils"
5
+ require "zlib"
6
+ require "forwardable"
7
+
8
+ module LogStash
9
+ module Outputs
10
+ class Qingstor
11
+ class TemporaryFileFactory
12
+ FILE_MODE = "a"
13
+ GZIP_ENCODING = "gzip"
14
+ GZIP_EXTENSION = "log.gz"
15
+ TXT_EXTENSION = "log"
16
+ STRFTIME = "%Y-%m-%dT%H.%M"
17
+
18
+ attr_accessor :counter, :tags, :prefix, :encoding, :tmpdir, :current
19
+
20
+ def initialize(prefix, tags, encoding, tmpdir)
21
+ @counter = 0
22
+ @prefix = prefix
23
+ @tags = tags
24
+ @encoding = encoding
25
+ @tmpdir = tmpdir
26
+ @lock = Mutex.new
27
+
28
+ rotate!
29
+ end
30
+
31
+ def rotate!
32
+ @lock.synchronize {
33
+ @current = new_file
34
+ increment_counter
35
+ @current
36
+ }
37
+ end
38
+
39
+ private
40
+ def extension
41
+ gzip? ? GZIP_EXTENSION : TXT_EXTENSION
42
+ end
43
+
44
+ def gzip?
45
+ encoding == GZIP_ENCODING
46
+ end
47
+
48
+ def increment_counter
49
+ @counter += 1
50
+ end
51
+
52
+ def current_time
53
+ Time.new.strftime(STRFTIME)
54
+ end
55
+
56
+ def generate_name
57
+ filename = "ls.qingstor.#{SecureRandom.uuid}.#{current_time}"
58
+
59
+ if tags.size > 0
60
+ "#{filename}.tag_#{tags.join('.')}.part#{counter}.#{extension}"
61
+ else
62
+ "#{filename}.part#{counter}.#{extension}"
63
+ end
64
+ end
65
+
66
+ def new_file
67
+ uuid = SecureRandom.uuid
68
+ name = generate_name
69
+ path = ::File.join(@tmpdir, uuid)
70
+ key = ::File.join(@prefix, name)
71
+
72
+ FileUtils.mkdir_p(::File.join(path, @prefix))
73
+
74
+ io = if gzip?
75
+ IOWrappedGzip.new(::File.open(::File.join(path, key), FILE_MODE))
76
+ else
77
+ ::File.open(::File.join(path, key), FILE_MODE)
78
+ end
79
+
80
+ TemporaryFile.new(key, io, path)
81
+ end
82
+
83
+ class IOWrappedGzip
84
+ extend Forwardable
85
+
86
+ def_delegators :@gzip_writer, :write, :close
87
+ attr_accessor :file_io, :gzip_writer
88
+
89
+ def initialize(file_io)
90
+ @file_io = file_io
91
+ @gzip_writer = Zlib::GzipWriter.open(file_io)
92
+ end
93
+
94
+ def path
95
+ @gzip_writer.to_io.path
96
+ end
97
+
98
+ def size
99
+ @gzip_writer.flush
100
+ @gzip_writer.to_io.size
101
+ end
102
+
103
+ def fsync
104
+ @gzip_writer.to_io.fsync
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end