logstash-output-google_cloud_storage 4.0.0-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 +77 -0
- data/CONTRIBUTORS +18 -0
- data/Gemfile +11 -0
- data/LICENSE +13 -0
- data/NOTICE.TXT +5 -0
- data/README.md +100 -0
- data/docs/index.asciidoc +305 -0
- data/lib/logstash-output-google_cloud_storage_jars.rb +35 -0
- data/lib/logstash/outputs/gcs/client.rb +88 -0
- data/lib/logstash/outputs/gcs/log_rotate.rb +77 -0
- data/lib/logstash/outputs/gcs/path_factory.rb +119 -0
- data/lib/logstash/outputs/gcs/temp_log_file.rb +111 -0
- data/lib/logstash/outputs/gcs/worker_pool.rb +47 -0
- data/lib/logstash/outputs/google_cloud_storage.rb +276 -0
- data/logstash-output-google_cloud_storage.gemspec +33 -0
- data/spec/fixtures/credentials.json +8 -0
- data/spec/outputs/gcs/client_spec.rb +18 -0
- data/spec/outputs/gcs/log_rotate_spec.rb +129 -0
- data/spec/outputs/gcs/path_factory_spec.rb +189 -0
- data/spec/outputs/gcs/temp_log_file_spec.rb +155 -0
- data/spec/outputs/gcs/worker_pool_spec.rb +29 -0
- data/spec/outputs/google_cloud_storage_spec.rb +23 -0
- data/spec/spec_helper.rb +3 -0
- data/vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-core/2.9.6/jackson-core-2.9.6.jar +0 -0
- data/vendor/jar-dependencies/com/google/api-client/google-api-client/1.23.0/google-api-client-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api-client/google-api-client/1.24.1/google-api-client-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/api-client/google-api-client/1.27.0/google-api-client-1.27.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/api-common/1.5.0/api-common-1.5.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/api-common/1.7.0/api-common-1.7.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax-httpjson/0.40.0/gax-httpjson-0.40.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax-httpjson/0.47.0/gax-httpjson-0.47.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax-httpjson/0.59.0/gax-httpjson-0.59.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax/1.23.0/gax-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax/1.30.0/gax-1.30.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/gax/1.42.0/gax-1.42.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/grpc/proto-google-common-protos/1.12.0/proto-google-common-protos-1.12.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/grpc/proto-google-common-protos/1.14.0/proto-google-common-protos-1.14.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/grpc/proto-google-common-protos/1.7.0/proto-google-common-protos-1.7.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/grpc/proto-google-iam-v1/0.12.0/proto-google-iam-v1-0.12.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/api/grpc/proto-google-iam-v1/0.8.0/proto-google-iam-v1-0.8.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/apis/google-api-services-storage/v1-rev114-1.23.0/google-api-services-storage-v1-rev114-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/apis/google-api-services-storage/v1-rev135-1.24.1/google-api-services-storage-v1-rev135-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/apis/google-api-services-storage/v1-rev20181109-1.27.0/google-api-services-storage-v1-rev20181109-1.27.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-credentials/0.10.0/google-auth-library-credentials-0.10.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-credentials/0.13.0/google-auth-library-credentials-0.13.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-credentials/0.9.0/google-auth-library-credentials-0.9.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-oauth2-http/0.10.0/google-auth-library-oauth2-http-0.10.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-oauth2-http/0.13.0/google-auth-library-oauth2-http-0.13.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/auth/google-auth-library-oauth2-http/0.9.0/google-auth-library-oauth2-http-0.9.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core-http/1.25.0/google-cloud-core-http-1.25.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core-http/1.39.0/google-cloud-core-http-1.39.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core-http/1.65.0/google-cloud-core-http-1.65.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core/1.25.0/google-cloud-core-1.25.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core/1.39.0/google-cloud-core-1.39.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-core/1.65.0/google-cloud-core-1.65.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-storage/1.25.0/google-cloud-storage-1.25.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-storage/1.39.0/google-cloud-storage-1.39.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/cloud/google-cloud-storage/1.65.0/google-cloud-storage-1.65.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar +0 -0
- data/vendor/jar-dependencies/com/google/code/gson/gson/2.7/gson-2.7.jar +0 -0
- data/vendor/jar-dependencies/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar +0 -0
- data/vendor/jar-dependencies/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/guava/guava-jdk5/17.0/guava-jdk5-17.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/guava/guava/20.0/guava-20.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/guava/guava/26.0-android/guava-26.0-android.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-apache/2.0.0/google-http-client-apache-2.0.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-appengine/1.23.0/google-http-client-appengine-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-appengine/1.24.1/google-http-client-appengine-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-appengine/1.28.0/google-http-client-appengine-1.28.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-jackson/1.23.0/google-http-client-jackson-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-jackson/1.24.1/google-http-client-jackson-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-jackson2/1.23.0/google-http-client-jackson2-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-jackson2/1.24.1/google-http-client-jackson2-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client-jackson2/1.28.0/google-http-client-jackson2-1.28.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client/1.23.0/google-http-client-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client/1.24.1/google-http-client-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/http-client/google-http-client/1.28.0/google-http-client-1.28.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/oauth-client/google-oauth-client/1.23.0/google-oauth-client-1.23.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/oauth-client/google-oauth-client/1.24.1/google-oauth-client-1.24.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/oauth-client/google-oauth-client/1.27.0/google-oauth-client-1.27.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/protobuf/protobuf-java-util/3.5.1/protobuf-java-util-3.5.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/protobuf/protobuf-java-util/3.6.0/protobuf-java-util-3.6.0.jar +0 -0
- data/vendor/jar-dependencies/com/google/protobuf/protobuf-java/3.5.1/protobuf-java-3.5.1.jar +0 -0
- data/vendor/jar-dependencies/com/google/protobuf/protobuf-java/3.6.0/protobuf-java-3.6.0.jar +0 -0
- data/vendor/jar-dependencies/commons-codec/commons-codec/1.3/commons-codec-1.3.jar +0 -0
- data/vendor/jar-dependencies/commons-codec/commons-codec/1.9/commons-codec-1.9.jar +0 -0
- data/vendor/jar-dependencies/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar +0 -0
- data/vendor/jar-dependencies/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
- data/vendor/jar-dependencies/io/grpc/grpc-context/1.12.0/grpc-context-1.12.0.jar +0 -0
- data/vendor/jar-dependencies/io/grpc/grpc-context/1.9.0/grpc-context-1.9.0.jar +0 -0
- data/vendor/jar-dependencies/io/opencensus/opencensus-api/0.11.1/opencensus-api-0.11.1.jar +0 -0
- data/vendor/jar-dependencies/io/opencensus/opencensus-api/0.15.0/opencensus-api-0.15.0.jar +0 -0
- data/vendor/jar-dependencies/io/opencensus/opencensus-contrib-http-util/0.11.1/opencensus-contrib-http-util-0.11.1.jar +0 -0
- data/vendor/jar-dependencies/io/opencensus/opencensus-contrib-http-util/0.15.0/opencensus-contrib-http-util-0.15.0.jar +0 -0
- data/vendor/jar-dependencies/joda-time/joda-time/2.9.2/joda-time-2.9.2.jar +0 -0
- data/vendor/jar-dependencies/org/apache/httpcomponents/httpclient/4.0.1/httpclient-4.0.1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3.jar +0 -0
- data/vendor/jar-dependencies/org/apache/httpcomponents/httpcore/4.0.1/httpcore-4.0.1.jar +0 -0
- data/vendor/jar-dependencies/org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6.jar +0 -0
- data/vendor/jar-dependencies/org/codehaus/jackson/jackson-core-asl/1.9.11/jackson-core-asl-1.9.11.jar +0 -0
- data/vendor/jar-dependencies/org/threeten/threetenbp/1.3.3/threetenbp-1.3.3.jar +0 -0
- metadata +249 -0
@@ -0,0 +1,276 @@
|
|
1
|
+
# [source,txt]
|
2
|
+
# -----
|
3
|
+
# encoding: utf-8
|
4
|
+
# Author: Rodrigo De Castro <rdc@google.com>
|
5
|
+
# Date: 2013-09-20
|
6
|
+
#
|
7
|
+
# Copyright 2013 Google Inc.
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
+
# you may not use this file except in compliance with the License.
|
11
|
+
# You may obtain a copy of the License at
|
12
|
+
#
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
14
|
+
#
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
18
|
+
# See the License for the specific language governing permissions and
|
19
|
+
# limitations under the License.
|
20
|
+
# -----
|
21
|
+
require 'logstash/outputs/gcs/client'
|
22
|
+
require "logstash/outputs/base"
|
23
|
+
require "logstash/outputs/gcs/path_factory"
|
24
|
+
require "logstash/outputs/gcs/worker_pool"
|
25
|
+
require "logstash/outputs/gcs/log_rotate"
|
26
|
+
require "logstash/namespace"
|
27
|
+
require "logstash/json"
|
28
|
+
require "stud/interval"
|
29
|
+
require "thread"
|
30
|
+
require "zlib"
|
31
|
+
|
32
|
+
# Summary: plugin to upload log events to Google Cloud Storage (GCS), rolling
|
33
|
+
# files based on the date pattern provided as a configuration setting. Events
|
34
|
+
# are written to files locally and, once file is closed, this plugin uploads
|
35
|
+
# it to the configured bucket.
|
36
|
+
#
|
37
|
+
# For more info on Google Cloud Storage, please go to:
|
38
|
+
# https://cloud.google.com/products/cloud-storage
|
39
|
+
#
|
40
|
+
# In order to use this plugin, a Google service account must be used. For
|
41
|
+
# more information, please refer to:
|
42
|
+
# https://developers.google.com/storage/docs/authentication#service_accounts
|
43
|
+
#
|
44
|
+
# Recommendation: experiment with the settings depending on how much log
|
45
|
+
# data you generate, so the uploader can keep up with the generated logs.
|
46
|
+
# Using gzip output can be a good option to reduce network traffic when
|
47
|
+
# uploading the log files and in terms of storage costs as well.
|
48
|
+
#
|
49
|
+
# USAGE:
|
50
|
+
# This is an example of logstash config:
|
51
|
+
#
|
52
|
+
# [source,json]
|
53
|
+
# --------------------------
|
54
|
+
# output {
|
55
|
+
# google_cloud_storage {
|
56
|
+
# bucket => "my_bucket" (required)
|
57
|
+
# json_key_file => "/path/to/privatekey.json" (optional)
|
58
|
+
# temp_directory => "/tmp/logstash-gcs" (optional)
|
59
|
+
# log_file_prefix => "logstash_gcs" (optional)
|
60
|
+
# max_file_size_kbytes => 1024 (optional)
|
61
|
+
# output_format => "plain" (optional)
|
62
|
+
# date_pattern => "%Y-%m-%dT%H:00" (optional)
|
63
|
+
# flush_interval_secs => 2 (optional)
|
64
|
+
# gzip => false (optional)
|
65
|
+
# gzip_content_encoding => false (optional)
|
66
|
+
# uploader_interval_secs => 60 (optional)
|
67
|
+
# upload_synchronous => false (optional)
|
68
|
+
# }
|
69
|
+
# }
|
70
|
+
# --------------------------
|
71
|
+
#
|
72
|
+
# Improvements TODO list:
|
73
|
+
# * Support logstash event variables to determine filename.
|
74
|
+
# * Turn Google API code into a Plugin Mixin (like AwsConfig).
|
75
|
+
# * There's no recover method, so if logstash/plugin crashes, files may not
|
76
|
+
# be uploaded to GCS.
|
77
|
+
# * Allow user to configure file name.
|
78
|
+
class LogStash::Outputs::GoogleCloudStorage < LogStash::Outputs::Base
|
79
|
+
config_name "google_cloud_storage"
|
80
|
+
|
81
|
+
concurrency :single
|
82
|
+
|
83
|
+
# GCS bucket name, without "gs://" or any other prefix.
|
84
|
+
config :bucket, :validate => :string, :required => true
|
85
|
+
|
86
|
+
# GCS path to private key file.
|
87
|
+
config :key_path, :validate => :string, :obsolete => 'Use json_key_file or ADC instead.'
|
88
|
+
|
89
|
+
# GCS private key password.
|
90
|
+
config :key_password, :validate => :string, :deprecated => 'Use json_key_file or ADC instead.'
|
91
|
+
|
92
|
+
# GCS service account.
|
93
|
+
config :service_account, :validate => :string, :deprecated => 'Use json_key_file or ADC instead.'
|
94
|
+
|
95
|
+
# Directory where temporary files are stored.
|
96
|
+
# Defaults to /tmp/logstash-gcs-<random-suffix>
|
97
|
+
config :temp_directory, :validate => :string, :default => ""
|
98
|
+
|
99
|
+
# Log file prefix. Log file will follow the format:
|
100
|
+
# <prefix>_hostname_date<.part?>.log
|
101
|
+
config :log_file_prefix, :validate => :string, :default => "logstash_gcs"
|
102
|
+
|
103
|
+
# Sets max file size in kbytes. 0 disable max file check.
|
104
|
+
config :max_file_size_kbytes, :validate => :number, :default => 10000
|
105
|
+
|
106
|
+
# The event format you want to store in files. Defaults to plain text.
|
107
|
+
config :output_format, :validate => [ "json", "plain" ], :default => "plain"
|
108
|
+
|
109
|
+
# Time pattern for log file, defaults to hourly files.
|
110
|
+
# Must Time.strftime patterns: www.ruby-doc.org/core-2.0/Time.html#method-i-strftime
|
111
|
+
config :date_pattern, :validate => :string, :default => "%Y-%m-%dT%H:00"
|
112
|
+
|
113
|
+
# Flush interval in seconds for flushing writes to log files. 0 will flush
|
114
|
+
# on every message.
|
115
|
+
config :flush_interval_secs, :validate => :number, :default => 2
|
116
|
+
|
117
|
+
# Gzip output stream when writing events to log files, set
|
118
|
+
# `Content-Type` to `application/gzip` instead of `text/plain`, and
|
119
|
+
# use file suffix `.log.gz` instead of `.log`.
|
120
|
+
config :gzip, :validate => :boolean, :default => false
|
121
|
+
|
122
|
+
# Gzip output stream when writing events to log files and set
|
123
|
+
# `Content-Encoding` to `gzip`.
|
124
|
+
config :gzip_content_encoding, :validate => :boolean, :default => false
|
125
|
+
|
126
|
+
# Uploader interval when uploading new files to GCS. Adjust time based
|
127
|
+
# on your time pattern (for example, for hourly files, this interval can be
|
128
|
+
# around one hour).
|
129
|
+
config :uploader_interval_secs, :validate => :number, :default => 60
|
130
|
+
|
131
|
+
# Should the hostname be included in the file name?
|
132
|
+
config :include_hostname, :validate => :boolean, :default => true
|
133
|
+
|
134
|
+
# Should a UUID be included in the file name?
|
135
|
+
config :include_uuid, :validate => :boolean, :default => false
|
136
|
+
|
137
|
+
# The path to the service account's JSON credentials file.
|
138
|
+
# Application Default Credentials (ADC) are used if the path is blank.
|
139
|
+
# See: https://cloud.google.com/docs/authentication/production
|
140
|
+
#
|
141
|
+
# You must run on GCP for ADC to work.
|
142
|
+
config :json_key_file, :validate => :string, :default => ""
|
143
|
+
|
144
|
+
# When true, files are uploaded by the event processing thread as soon as a file is ready.
|
145
|
+
# When false, (the default behaviour), files will be uploaded in a dedicated thread.
|
146
|
+
#
|
147
|
+
# Enabling this option provides greater likelihood that all generated files will be
|
148
|
+
# to GCS, especially in the event of a graceful shutdown of logstash, such as when an
|
149
|
+
# input plugin reaches the end of events. This comes at the price of introducing delays
|
150
|
+
# in the event processing pipeline as files are uploaded.
|
151
|
+
#
|
152
|
+
# When this feature is enabled, the uploader_interval_secs option has no effect.
|
153
|
+
config :upload_synchronous, :validate => :boolean, :default => false
|
154
|
+
|
155
|
+
config :max_concurrent_uploads, :validate => :number, :default => 5
|
156
|
+
|
157
|
+
public
|
158
|
+
def register
|
159
|
+
@logger.debug('Registering Google Cloud Storage plugin')
|
160
|
+
|
161
|
+
@workers = LogStash::Outputs::Gcs::WorkerPool.new(@max_concurrent_uploads, @upload_synchronous)
|
162
|
+
initialize_temp_directory
|
163
|
+
initialize_path_factory
|
164
|
+
initialize_log_rotater
|
165
|
+
|
166
|
+
initialize_google_client
|
167
|
+
|
168
|
+
start_uploader
|
169
|
+
|
170
|
+
@content_type = @gzip ? 'application/gzip' : 'text/plain'
|
171
|
+
@content_encoding = @gzip_content_encoding ? 'gzip' : 'identity'
|
172
|
+
end
|
173
|
+
|
174
|
+
# Method called for each log event. It writes the event to the current output
|
175
|
+
# file, flushing depending on flush interval configuration.
|
176
|
+
public
|
177
|
+
def receive(event)
|
178
|
+
@logger.debug('Received event', :event => event)
|
179
|
+
|
180
|
+
if @output_format == 'json'
|
181
|
+
message = LogStash::Json.dump(event.to_hash)
|
182
|
+
else
|
183
|
+
message = event.to_s
|
184
|
+
end
|
185
|
+
|
186
|
+
@log_rotater.writeln(message)
|
187
|
+
end
|
188
|
+
|
189
|
+
public
|
190
|
+
def close
|
191
|
+
@logger.debug('Stopping the plugin, uploading the remaining files.')
|
192
|
+
Stud.stop!(@registration_thread) unless @registration_thread.nil?
|
193
|
+
|
194
|
+
# Force rotate the log. If it contains data it will be submitted
|
195
|
+
# to the work pool and will be uploaded before the plugin stops.
|
196
|
+
@log_rotater.rotate_log!
|
197
|
+
@workers.stop!
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
##
|
203
|
+
# Creates temporary directory, if it does not exist.
|
204
|
+
#
|
205
|
+
# A random suffix is appended to the temporary directory
|
206
|
+
def initialize_temp_directory
|
207
|
+
require "stud/temporary"
|
208
|
+
|
209
|
+
if @temp_directory.empty?
|
210
|
+
@temp_directory = Stud::Temporary.directory('logstash-gcs')
|
211
|
+
end
|
212
|
+
|
213
|
+
FileUtils.mkdir_p(@temp_directory) unless File.directory?(@temp_directory)
|
214
|
+
|
215
|
+
@logger.info("Using temporary directory: #{@temp_directory}")
|
216
|
+
end
|
217
|
+
|
218
|
+
def initialize_path_factory
|
219
|
+
@path_factory = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
|
220
|
+
builder.set_directory(@temp_directory)
|
221
|
+
builder.set_prefix(@log_file_prefix)
|
222
|
+
builder.set_include_host(@include_hostname)
|
223
|
+
builder.set_date_pattern(@date_pattern)
|
224
|
+
builder.set_include_part(@max_file_size_kbytes > 0)
|
225
|
+
builder.set_include_uuid(@include_uuid)
|
226
|
+
builder.set_is_gzipped(@gzip)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# start_uploader periodically sends flush events through the log rotater
|
231
|
+
def start_uploader
|
232
|
+
@registration_thread = Thread.new do
|
233
|
+
Stud.interval(@uploader_interval_secs) do
|
234
|
+
@log_rotater.writeln(nil)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
##
|
240
|
+
# Initializes Google Client instantiating client and authorizing access.
|
241
|
+
def initialize_google_client
|
242
|
+
@client = LogStash::Outputs::Gcs::Client.new(@bucket, @json_key_file, @logger)
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Uploads a local file to the configured bucket.
|
247
|
+
def upload_object(filename)
|
248
|
+
@client.upload_object(filename, @content_encoding, @content_type)
|
249
|
+
end
|
250
|
+
|
251
|
+
def upload_and_delete(filename)
|
252
|
+
file_size = File.stat(filename).size
|
253
|
+
|
254
|
+
if file_size > 0
|
255
|
+
upload_object(filename)
|
256
|
+
else
|
257
|
+
@logger.debug('File size is zero, skip upload.', :filename => filename)
|
258
|
+
end
|
259
|
+
|
260
|
+
@logger.debug('Delete local temporary file', :filename => filename)
|
261
|
+
File.delete(filename)
|
262
|
+
end
|
263
|
+
|
264
|
+
def initialize_log_rotater
|
265
|
+
max_file_size = @max_file_size_kbytes * 1024
|
266
|
+
@log_rotater = LogStash::Outputs::Gcs::LogRotate.new(@path_factory, max_file_size, @gzip, @flush_interval_secs, @gzip_content_encoding)
|
267
|
+
|
268
|
+
@log_rotater.on_rotate do |filename|
|
269
|
+
@logger.info("Rotated out file: #{filename}")
|
270
|
+
@workers.post do
|
271
|
+
upload_and_delete(filename)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
attr_accessor :active
|
276
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'logstash-output-google_cloud_storage'
|
3
|
+
s.version = '4.0.0'
|
4
|
+
s.licenses = ['Apache-2.0']
|
5
|
+
s.summary = "plugin to upload log events to Google Cloud Storage (GCS)"
|
6
|
+
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
7
|
+
s.authors = ["Elastic"]
|
8
|
+
s.email = 'info@elastic.co'
|
9
|
+
s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
|
10
|
+
s.require_paths = ["lib", "vendor/jar-dependencies"]
|
11
|
+
|
12
|
+
# Files
|
13
|
+
s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
|
14
|
+
|
15
|
+
# Tests
|
16
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
17
|
+
|
18
|
+
# Special flag to let us know this is actually a logstash plugin
|
19
|
+
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
|
20
|
+
|
21
|
+
# Gem dependencies
|
22
|
+
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
23
|
+
|
24
|
+
s.add_runtime_dependency 'stud'
|
25
|
+
s.add_runtime_dependency 'logstash-codec-plain'
|
26
|
+
s.add_runtime_dependency 'mime-types', '~> 2' # last version compatible with ruby 2.x
|
27
|
+
s.add_runtime_dependency 'concurrent-ruby' # use version bundled with Logstash to avoid platform mismatch on plugin install
|
28
|
+
s.add_development_dependency 'logstash-devutils'
|
29
|
+
|
30
|
+
# JARs
|
31
|
+
s.platform = 'java'
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"//": "Dummy account from https://github.com/GoogleCloudPlatform/google-cloud-java/google-cloud-clients/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java",
|
3
|
+
"private_key_id": "somekeyid",
|
4
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+K2hSuFpAdrJI\nnCgcDz2M7t7bjdlsadsasad+fvRSW6TjNQZ3p5LLQY1kSZRqBqylRkzteMOyHgaR\n0Pmxh3ILCND5men43j3h4eDbrhQBuxfEMalkG92sL+PNQSETY2tnvXryOvmBRwa/\nQP/9dJfIkIDJ9Fw9N4Bhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\nknddadwkwewcVxHFhcZJO+XWf6ofLUXpRwiTZakGMn8EE1uVa2LgczOjwWHGi99MFjxSer5m9\n1tCa3/KEGKiS/YL71JvjwX3mb+cewlkcmweBKZHM2JPTk0ZednFSpVZMtycjkbLa\ndYOS8V85AgMBewECggEBAKksaldajfDZDV6nGqbFjMiizAKJolr/M3OQw16K6o3/\n0S31xIe3sSlgW0+UbYlF4U8KifhManD1apVSC3csafaspP4RZUHFhtBywLO9pR5c\nr6S5aLp+gPWFyIp1pfXbWGvc5VY/v9x7ya1VEa6rXvLsKupSeWAW4tMj3eo/64ge\nsdaceaLYw52KeBYiT6+vpsnYrEkAHO1fF/LavbLLOFJmFTMxmsNaG0tuiJHgjshB\n82DpMCbXG9YcCgI/DbzuIjsdj2JC1cascSP//3PmefWysucBQe7Jryb6NQtASmnv\nCdDw/0jmZTEjpe4S1lxfHplAhHFtdgYTvyYtaLZiVVkCgYEA8eVpof2rceecw/I6\n5ng1q3Hl2usdWV/4mZMvR0fOemacLLfocX6IYxT1zA1FFJlbXSRsJMf/Qq39mOR2\nSpW+hr4jCoHeRVYLgsbggtrevGmILAlNoqCMpGZ6vDmJpq6ECV9olliDvpPgWOP+\nmYPDreFBGxWvQrADNbRt2dmGsrsCgYEAyUHqB2wvJHFqdmeBsaacewzV8x9WgmeX\ngUIi9REwXlGDW0Mz50dxpxcKCAYn65+7TCnY5O/jmL0VRxU1J2mSWyWTo1C+17L0\n3fUqjxL1pkefwecxwecvC+gFFYdJ4CQ/MHHXU81Lwl1iWdFCd2UoGddYaOF+KNeM\nHC7cmqra+JsCgYEAlUNywzq8nUg7282E+uICfCB0LfwejuymR93CtsFgb7cRd6ak\nECR8FGfCpH8ruWJINllbQfcHVCX47ndLZwqv3oVFKh6pAS/vVI4dpOepP8++7y1u\ncoOvtreXCX6XqfrWDtKIvv0vjlHBhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\nkndj5uNl5SiuVxHFhcZJO+XWf6ofLUregtevZakGMn8EE1uVa2AY7eafmoU/nZPT\n00YB0TBATdCbn/nBSuKDESkhSg9s2GEKQZG5hBmL5uCMfo09z3SfxZIhJdlerreP\nJ7gSidI12N+EZxYd4xIJh/HFDgp7RRO87f+WJkofMQKBgGTnClK1VMaCRbJZPriw\nEfeFCoOX75MxKwXs6xgrw4W//AYGGUjDt83lD6AZP6tws7gJ2IwY/qP7+lyhjEqN\nHtfPZRGFkGZsdaksdlaksd323423d+15/UvrlRSFPNj1tWQmNKkXyRDW4IG1Oa2p\nrALStNBx5Y9t0/LQnFI4w3aG\n-----END PRIVATE KEY-----\n",
|
5
|
+
"client_email": "someclientid@developer.gserviceaccount.com",
|
6
|
+
"client_id": "someclientid.apps.googleusercontent.com",
|
7
|
+
"type": "service_account"
|
8
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'logstash/outputs/gcs/client'
|
4
|
+
|
5
|
+
describe LogStash::Outputs::Gcs::Client do
|
6
|
+
|
7
|
+
# This test is mostly to make sure the Java types, signatures and classes
|
8
|
+
# haven't changed being that JRuby is very relaxed.
|
9
|
+
describe '#initialize' do
|
10
|
+
let(:logger) { spy('logger') }
|
11
|
+
|
12
|
+
it 'does not throw an error when initializing' do
|
13
|
+
key_file = ::File.join('spec', 'fixtures', 'credentials.json')
|
14
|
+
key_file = ::File.absolute_path(key_file)
|
15
|
+
LogStash::Outputs::Gcs::Client.new('my-bucket', key_file, logger)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'logstash/outputs/gcs/log_rotate'
|
3
|
+
require 'logstash/outputs/gcs/path_factory'
|
4
|
+
require 'logstash/outputs/gcs/temp_log_file'
|
5
|
+
|
6
|
+
describe LogStash::Outputs::Gcs::LogRotate do
|
7
|
+
let(:tempdir){ Stud::Temporary.directory }
|
8
|
+
let(:path_factory) do
|
9
|
+
LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
|
10
|
+
builder.set_directory tempdir
|
11
|
+
builder.set_prefix 'prefix'
|
12
|
+
builder.set_include_host true
|
13
|
+
builder.set_date_pattern ''
|
14
|
+
builder.set_include_part true
|
15
|
+
builder.set_include_uuid true
|
16
|
+
builder.set_is_gzipped true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
let(:open_file_1) { double('open-temp-1', :size => 5, :path => 'one', :close! => true, :time_since_sync => 10, :fsync => true)}
|
20
|
+
let(:open_file_2) { double('open-temp-2', :size => 5, :path => 'two', :close! => true, :time_since_sync => 60, :fsync => true)}
|
21
|
+
|
22
|
+
describe '#initialize' do
|
23
|
+
it 'opens the first file' do
|
24
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
|
25
|
+
|
26
|
+
LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#writeln' do
|
31
|
+
subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
|
32
|
+
|
33
|
+
it 'does not rotate if size is small and path is the same' do
|
34
|
+
expect(path_factory).to receive(:should_rotate?).and_return(false)
|
35
|
+
# once for init
|
36
|
+
expect(path_factory).to receive(:rotate_path!).once
|
37
|
+
|
38
|
+
subject.writeln('foo')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'rotates the file if the size is too big' do
|
42
|
+
# once for init, once for writeln
|
43
|
+
expect(path_factory).to receive(:rotate_path!).twice
|
44
|
+
|
45
|
+
subject.writeln('this line is longer than ten characters' * 1000)
|
46
|
+
subject.writeln('flush')
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'rotates the file if the path changed' do
|
50
|
+
expect(path_factory).to receive(:should_rotate?).and_return(true)
|
51
|
+
# once for init, once for writeln
|
52
|
+
expect(path_factory).to receive(:rotate_path!).twice
|
53
|
+
|
54
|
+
subject.writeln('foo')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'writes the message' do
|
58
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
|
59
|
+
expect(open_file_1).to receive(:write).with('foo', "\n")
|
60
|
+
|
61
|
+
subject.writeln('foo')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not write nil messages' do
|
65
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
|
66
|
+
expect(open_file_1).not_to receive(:write)
|
67
|
+
|
68
|
+
subject.writeln(nil)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'does not fsync if delta less than limit' do
|
72
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
|
73
|
+
expect(open_file_1).not_to receive(:fsync)
|
74
|
+
|
75
|
+
subject.writeln(nil)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'fsyncs if delta greater than limit' do
|
79
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_2)
|
80
|
+
expect(open_file_2).to receive(:fsync)
|
81
|
+
|
82
|
+
subject.writeln(nil)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#rotate_log!' do
|
87
|
+
subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
|
88
|
+
|
89
|
+
before :each do
|
90
|
+
allow(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1, open_file_2)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'closes the old file' do
|
94
|
+
expect(open_file_1).to receive(:close!)
|
95
|
+
|
96
|
+
subject.rotate_log!
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'calls the callback with the old file name' do
|
100
|
+
value = nil
|
101
|
+
subject.on_rotate { |old_path| value = old_path }
|
102
|
+
|
103
|
+
subject.rotate_log!
|
104
|
+
expect(value).to eq(open_file_1.path)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'opens a new file based on the new path' do
|
108
|
+
expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1, open_file_2)
|
109
|
+
expect(open_file_2).to receive(:write).with('foo', "\n")
|
110
|
+
|
111
|
+
subject.rotate_log!
|
112
|
+
subject.writeln('foo')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#on_rotate' do
|
117
|
+
subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
|
118
|
+
|
119
|
+
it 'replaces an existing callback' do
|
120
|
+
value = :none
|
121
|
+
|
122
|
+
subject.on_rotate { value = :first }
|
123
|
+
subject.on_rotate { value = :second }
|
124
|
+
|
125
|
+
subject.rotate_log!
|
126
|
+
expect(value).to eq(:second)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|