logstash-input-sfdc_elf_schneider 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +41 -0
- data/Gemfile +9 -0
- data/MIT-LICENSE +7 -0
- data/README.md +65 -0
- data/Rakefile +13 -0
- data/lib/logstash/inputs/sfdc_elf.rb +147 -0
- data/lib/logstash/inputs/sfdc_elf/client_with_streaming_support.rb +61 -0
- data/lib/logstash/inputs/sfdc_elf/queue_util.rb +176 -0
- data/lib/logstash/inputs/sfdc_elf/scheduler.rb +73 -0
- data/lib/logstash/inputs/sfdc_elf/state_persistor.rb +49 -0
- data/logstash-input-sfdc_elf_schneider.gemspec +29 -0
- data/spec/fixtures/auth_success_response.json +7 -0
- data/spec/fixtures/describe.json +3526 -0
- data/spec/fixtures/org_query_response.json +11 -0
- data/spec/fixtures/queue_util/create_event_ELF_list1.json +19 -0
- data/spec/fixtures/queue_util/create_event_ELF_list2.json +29 -0
- data/spec/fixtures/queue_util/create_event_ELF_list3.json +29 -0
- data/spec/fixtures/queue_util/create_event_sampledata1.csv +31 -0
- data/spec/fixtures/queue_util/create_event_sampledata2.csv +2 -0
- data/spec/fixtures/queue_util/create_event_sampledata3.csv +2 -0
- data/spec/fixtures/queue_util/eventlogfile_describe.json +990 -0
- data/spec/fixtures/queue_util/eventlogfile_list.json +62 -0
- data/spec/fixtures/queue_util/sample_data1.csv +5 -0
- data/spec/fixtures/queue_util/sample_data2.csv +5 -0
- data/spec/fixtures/queue_util/sample_data3.csv +1467 -0
- data/spec/fixtures/queue_util/sample_data4.csv +2 -0
- data/spec/fixtures/queue_util/sample_data5.csv +309 -0
- data/spec/inputs/sfdc_elf/queue_util_spec.rb +176 -0
- data/spec/inputs/sfdc_elf/scheduler_spec.rb +56 -0
- data/spec/inputs/sfdc_elf/state_persistor_spec.rb +58 -0
- data/spec/inputs/sfdc_elf_spec.rb +101 -0
- data/spec/spec_helper.rb +38 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 152dbc61c42b31c6dae7a176404074ca2614d8717006d5d7c101a4b7b45efdd7
|
4
|
+
data.tar.gz: d1d649228b720404964e7cccc34c49ce409e7e2dd786adbfdc7376972f270003
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4fe45fe63f01fe4d93d1f42383613c55372a88d668a05bad355ec37c253f011182608580bc0d8079af50ca22aa286f6bde543d2d75f535e7151af3a247f891f1
|
7
|
+
data.tar.gz: c56974381aae6b844f113b5f417f8e427c1c2e34bde92bad52a80ddf52dc2e7c1f9d7f629d23cd7b398b76f5ca851ba3ad20978b04d05f9d3fbdf193608aa19e
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Must be compact, so that Logstash can locate plugins via "LogStash::inputs::YourPluginName"
|
2
|
+
Style/ClassAndModuleChildren:
|
3
|
+
EnforcedStyle: compact
|
4
|
+
|
5
|
+
# ABC is a metric based on the number of Assignments, Branches, and Conditions.
|
6
|
+
# More info: http://c2.com/cgi/wiki?AbcMetric
|
7
|
+
AbcSize:
|
8
|
+
Max: 25
|
9
|
+
|
10
|
+
# Disable messages about using fail instead of raise for a specific try/catch block.
|
11
|
+
SignalException:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# Disable message about providing an exception class and message as an argument to raise.
|
15
|
+
RaiseArgs:
|
16
|
+
EnforcedStyle: compact
|
17
|
+
|
18
|
+
Metrics/LineLength:
|
19
|
+
AllowURI: true
|
20
|
+
Max: 120
|
21
|
+
|
22
|
+
Metrics/MethodLength:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Metrics/ClassLength:
|
26
|
+
Max: 150
|
27
|
+
|
28
|
+
EmptyLines:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
# Allow to have no line break between method and modifier. Example using public or private, then method.
|
32
|
+
Style/EmptyLinesAroundAccessModifier:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
# Allow to move chained dot methods to the next line.
|
36
|
+
Style/DotPosition:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
# Allow to use the word "get" in accessor method names.
|
40
|
+
Style/AccessorMethodName:
|
41
|
+
Enabled: false
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2015 salesforce.com, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Logtypes support
|
2
|
+
|
3
|
+
we only support 'ApexExecution','ApexSoap','ApexTrigger','API','BulkApi','Dashboard','LightningError','LightningInteraction','LightningPageView','LightningPerformance','LoginAs','Login','Logout','MetadataApiOperation','Report','RestApi','URI','VisualforceRequest','WaveChange','WaveInteraction','WavePerformance' log types.
|
4
|
+
|
5
|
+
# Logstash Plugin for Salesforce's Event Log File
|
6
|
+
|
7
|
+
The Salesforce Event Log File [Ruby Gem](https://rubygems.org/gems/logstash-input-sfdc_elf/) plug-in simplifies the integration between
|
8
|
+
the ELK stack and Event Log Files by allowing you to easily download and index Salesforce event data every day without a custom integration.
|
9
|
+
|
10
|
+
ELF on ELK on Docker is the quickest way to get started. Check the links below.
|
11
|
+
|
12
|
+
### Logstash Plugin Config
|
13
|
+
|
14
|
+
```
|
15
|
+
input {
|
16
|
+
sfdc_elf {
|
17
|
+
# === Required Fields ===
|
18
|
+
|
19
|
+
# Input the username and password of your Force.com organization.
|
20
|
+
username => ""
|
21
|
+
password => ""
|
22
|
+
|
23
|
+
# Id & Secret are values that are generated when you create a "Connected App."
|
24
|
+
client_id => ""
|
25
|
+
client_secret => ""
|
26
|
+
|
27
|
+
|
28
|
+
# === OPTIONAL FIELDS ===
|
29
|
+
# Leave these fields commented out (with '#') if the config does not apply to you. Some of the fields
|
30
|
+
# have default values
|
31
|
+
|
32
|
+
# Only needed when your Force.com organization requires it.
|
33
|
+
# security_token => ""
|
34
|
+
|
35
|
+
# The path to be use to store the .sfdc_info_logstash state persistor file. You set the path
|
36
|
+
# like so, "~/SomeDirectory" Paths must be absolute and cannot be relative.
|
37
|
+
# Defaults to your home directory "~/".
|
38
|
+
# path => ""
|
39
|
+
|
40
|
+
# Specify how often the plugin should grab new data in terms of minutes.
|
41
|
+
# Defaults to 1440 minutes (1 day). We recommend keeping it at 1 day.
|
42
|
+
# poll_interval_in_minutes => 60
|
43
|
+
|
44
|
+
# The host to use for OAuth2 authentication.
|
45
|
+
# Defaluts to "login.salesforce.com".
|
46
|
+
# Use "test.salesforce.com" for connecting to Sandbox instance.
|
47
|
+
# host => ""
|
48
|
+
|
49
|
+
}
|
50
|
+
}
|
51
|
+
```
|
52
|
+
|
53
|
+
### Setting up a Salesforce Connected App
|
54
|
+
|
55
|
+
Detailed instructions for setting up a Connected App can be found [here](https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm).
|
56
|
+
When configuring the connected application, ensure the following options are configured:
|
57
|
+
|
58
|
+
1. _Enable OAuth Settings_ is checked.
|
59
|
+
2. _Access and manage data (api)_ and _Access your basic information (id, profile, email, address, phone)_ are included in your _Selected OAuth Scopes_.
|
60
|
+
|
61
|
+
### Blogs, Articles, and Tutorials
|
62
|
+
|
63
|
+
1. [Elf on Elk on Docker](http://www.salesforcehacker.com/2015/10/elf-on-elk-on-docker.html) by Adam Torman
|
64
|
+
2. [Elf on Elk on Docker Image Source Code](https://github.com/developerforce/elf_elk_docker/blob/master/README.md)
|
65
|
+
3. ['Users: WE KNOW THEM' – The ELF@Salesforce](https://www.elastic.co/elasticon/conf/2016/sf/users-we-know-them-the-elf-at-salesforce) at Elastic{ON} '16 by Adam Torman and Abhishek Sreenivasa
|
data/Rakefile
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'logstash/inputs/base'
|
3
|
+
require 'logstash/namespace'
|
4
|
+
require_relative 'sfdc_elf/client_with_streaming_support'
|
5
|
+
require_relative 'sfdc_elf/queue_util'
|
6
|
+
require_relative 'sfdc_elf/state_persistor'
|
7
|
+
require_relative 'sfdc_elf/scheduler'
|
8
|
+
|
9
|
+
# This plugin enables Salesforce customers to load EventLogFile(ELF) data from their Force.com orgs. The plugin will
|
10
|
+
# handle downloading ELF CSV file, parsing them, and handling any schema changes transparently.
|
11
|
+
class LogStash::Inputs::SfdcElf < LogStash::Inputs::Base
|
12
|
+
LOG_KEY = 'SFDC'
|
13
|
+
RETRY_ATTEMPTS = 3
|
14
|
+
|
15
|
+
config_name 'sfdc_elf'
|
16
|
+
default :codec, 'plain'
|
17
|
+
|
18
|
+
# Username to your Force.com organization.
|
19
|
+
config :username, validate: :string, required: true
|
20
|
+
|
21
|
+
# Password to your Force.com organization.
|
22
|
+
config :password, validate: :password, required: true
|
23
|
+
|
24
|
+
# Client id to your Force.com organization.
|
25
|
+
config :client_id, validate: :password, required: true
|
26
|
+
|
27
|
+
# Client secret to your Force.com organization.
|
28
|
+
config :client_secret, validate: :password, required: true
|
29
|
+
|
30
|
+
# The host to use for OAuth2 authentication.
|
31
|
+
config :host, validate: :string, default: 'login.salesforce.com'
|
32
|
+
|
33
|
+
# Only needed when your Force.com organization requires it.
|
34
|
+
# Security token to you Force.com organization, can be found in My Settings > Personal > Reset My Security Token.
|
35
|
+
# Then it will take you to "Reset My Security Token" page, and click on the "Reset Security Token" button. The token
|
36
|
+
# will be emailed to you.
|
37
|
+
config :security_token, validate: :password, default: ''
|
38
|
+
|
39
|
+
# The path to be use to store the .sfdc_info_logstash state persistor file. You set the path like so, `~/SomeDirectory` Paths must be
|
40
|
+
# absolute and cannot be relative.
|
41
|
+
config :path, validate: :string, default: Dir.home
|
42
|
+
|
43
|
+
# Specify how often the plugin should grab new data in terms of minutes.
|
44
|
+
config :poll_interval_in_minutes, validate: [*1..(24 * 60)], default: (24 * 60)
|
45
|
+
|
46
|
+
|
47
|
+
# The first part of logstash pipeline is register, where all instance variables are initialized.
|
48
|
+
|
49
|
+
public
|
50
|
+
def register
|
51
|
+
# Initialize the client.
|
52
|
+
@client = ClientWithStreamingSupport.new
|
53
|
+
@client.client_id = @client_id.value
|
54
|
+
@client.client_secret = @client_secret.value
|
55
|
+
@client.host = @host
|
56
|
+
@client.version = '33.0'
|
57
|
+
|
58
|
+
# Authenticate the client
|
59
|
+
@logger.info("#{LOG_KEY}: tyring to authenticate client")
|
60
|
+
@client.retryable_authenticate(username: @username,
|
61
|
+
password: @password.value + @security_token.value,
|
62
|
+
retry_attempts: RETRY_ATTEMPTS)
|
63
|
+
@logger.info("#{LOG_KEY}: authenticating succeeded")
|
64
|
+
|
65
|
+
# Save org id to distinguish between multiple orgs.
|
66
|
+
@org_id = @client.query('select id from Organization')[0]['Id']
|
67
|
+
|
68
|
+
# Set up time interval for forever while loop.
|
69
|
+
@poll_interval_in_seconds = @poll_interval_in_minutes * 60
|
70
|
+
|
71
|
+
# Handel the @path config passed by the user. If path does not exist then set @path to home directory.
|
72
|
+
verify_path
|
73
|
+
|
74
|
+
# Handel parsing the data into event objects and enqueue it to the queue.
|
75
|
+
@queue_util = QueueUtil.new
|
76
|
+
|
77
|
+
# Handel when to schedule the next process based on the @poll_interval_in_hours config.
|
78
|
+
@scheduler = Scheduler.new(@poll_interval_in_seconds)
|
79
|
+
|
80
|
+
# Handel state of the plugin based on the read and writes of LogDates to the .sdfc_info_logstash file.
|
81
|
+
@state_persistor = StatePersistor.new(@path, @org_id)
|
82
|
+
|
83
|
+
# Grab the last indexed log date.
|
84
|
+
@last_indexed_log_date = @state_persistor.get_last_indexed_log_date
|
85
|
+
@logger.info("#{LOG_KEY}: @last_indexed_log_date = #{@last_indexed_log_date}")
|
86
|
+
end # def register
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
# The second stage of Logstash pipeline is run, where it expects to parse your data into event objects and then pass
|
92
|
+
# it into the queue to be used in the rest of the pipeline.
|
93
|
+
|
94
|
+
public
|
95
|
+
def run(queue)
|
96
|
+
@scheduler.schedule do
|
97
|
+
# Line for readable log statements.
|
98
|
+
@logger.info('---------------------------------------------------')
|
99
|
+
|
100
|
+
# Grab a list of SObjects, specifically EventLogFiles.
|
101
|
+
soql_expr = "SELECT Id, EventType, Logfile, LogDate, LogFileLength, LogFileFieldTypes
|
102
|
+
FROM EventLogFile
|
103
|
+
WHERE LogDate > #{@last_indexed_log_date} and EventType in ('ApexExecution','ApexSoap','ApexTrigger','API','BulkApi','Dashboard','LightningError','LightningInteraction','LightningPageView','LightningPerformance','LoginAs','Login','Logout','MetadataApiOperation','Report','RestApi','URI','VisualforceRequest','WaveChange','WaveInteraction','WavePerformance') ORDER BY LogDate ASC "
|
104
|
+
|
105
|
+
|
106
|
+
query_result_list = @client.retryable_query(username: @username,
|
107
|
+
password: @password.value + @security_token.value,
|
108
|
+
retry_attempts: RETRY_ATTEMPTS,
|
109
|
+
soql_expr: soql_expr)
|
110
|
+
|
111
|
+
@logger.info("#{LOG_KEY}: query result size = #{query_result_list.size}")
|
112
|
+
|
113
|
+
if !query_result_list.empty?
|
114
|
+
# query_result_list is in ascending order based on the LogDate, so grab the last one of the list and save the
|
115
|
+
# LogDate to @last_read_log_date and .sfdc_info_logstash
|
116
|
+
@last_indexed_log_date = query_result_list.last.LogDate.strftime('%FT%T.%LZ')
|
117
|
+
|
118
|
+
# TODO: grab tempfiles here!!
|
119
|
+
|
120
|
+
# Overwrite the .sfdc_info_logstash file with the @last_read_log_date.
|
121
|
+
# Note: we currently do not support deduplication, but will implement it soon.
|
122
|
+
# TODO: need to implement deduplication
|
123
|
+
# TODO: might have to move this after enqueue_events(), in case of a crash in between.
|
124
|
+
# TODO: can do all @state_persistor calls after the if statement
|
125
|
+
@state_persistor.update_last_indexed_log_date(@last_indexed_log_date)
|
126
|
+
|
127
|
+
# Creates events from query_result_list, then simply append the events to the queue.
|
128
|
+
@queue_util.enqueue_events(query_result_list, queue, @client)
|
129
|
+
end
|
130
|
+
end # do loop
|
131
|
+
end # def run
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
# Handel the @path variable passed by the user. If path does not exist then set @path to home directory.
|
137
|
+
|
138
|
+
private
|
139
|
+
def verify_path
|
140
|
+
# Check if the path exist, if not then set @path to home directory.
|
141
|
+
unless File.directory?(@path)
|
142
|
+
@logger.warn("#{LOG_KEY}: provided path does not exist or is invalid. path=#{@path}")
|
143
|
+
@path = Dir.home
|
144
|
+
end
|
145
|
+
@logger.info("#{LOG_KEY}: path = #{@path}")
|
146
|
+
end
|
147
|
+
end # class LogStash::inputs::File
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'databasedotcom'
|
3
|
+
|
4
|
+
# This class subclasses Databasedotcom Client object and added steaming
|
5
|
+
# downloading and retryable authentication and retryable query.
|
6
|
+
class ClientWithStreamingSupport < Databasedotcom::Client
|
7
|
+
# Constants
|
8
|
+
# LOG_KEY = 'SFDC - ClientWithStreamingSupport'
|
9
|
+
#
|
10
|
+
# def initialize
|
11
|
+
# @logger = Cabin::Channel.get(LogStash)
|
12
|
+
# end
|
13
|
+
|
14
|
+
def streaming_download(path, output_stream)
|
15
|
+
connection = Net::HTTP.new(URI.parse(instance_url).host, 443)
|
16
|
+
connection.use_ssl = true
|
17
|
+
encoded_path = URI.escape(path)
|
18
|
+
|
19
|
+
req = Net::HTTP::Get.new(encoded_path, 'Authorization' => "OAuth #{oauth_token}")
|
20
|
+
connection.request(req) do |response|
|
21
|
+
raise SalesForceError.new(response) unless response.is_a?(Net::HTTPSuccess)
|
22
|
+
response.read_body do |chunk|
|
23
|
+
output_stream.write chunk
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# This helper method is called whenever we need to initaialize the client
|
29
|
+
# object or whenever the client token expires. It will attempt 3 times
|
30
|
+
# with a 30 second delay between each retry. On the 3th try, if it fails the
|
31
|
+
# exception will be raised.
|
32
|
+
|
33
|
+
def retryable_authenticate(options = {})
|
34
|
+
1.upto(options[:retry_attempts]) do |count|
|
35
|
+
begin
|
36
|
+
# If exception is not thrown, then break out of loop.
|
37
|
+
authenticate(username: options[:username], password: options[:password])
|
38
|
+
break
|
39
|
+
rescue StandardError => e
|
40
|
+
# Sleep for 30 seconds 2 times. On the 3th time if it fails raise the exception without sleeping.
|
41
|
+
if (count == options[:retry_attempts])
|
42
|
+
raise e
|
43
|
+
else
|
44
|
+
sleep(30)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end # def authenticate
|
49
|
+
|
50
|
+
|
51
|
+
def retryable_query(options = {})
|
52
|
+
query(options[:soql_expr])
|
53
|
+
rescue Databasedotcom::SalesForceError => e
|
54
|
+
# Session has expired. Force user logout, then re-authenticate.
|
55
|
+
if e.message == 'Session expired or invalid'
|
56
|
+
retryable_authenticate(options)
|
57
|
+
else
|
58
|
+
raise e
|
59
|
+
end
|
60
|
+
end # def retryable_query
|
61
|
+
end # ClientWithStreamingSupport
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'csv'
|
3
|
+
require 'resolv'
|
4
|
+
|
5
|
+
# Handel parsing data into event objects and then enqueue all of the events to the queue.
|
6
|
+
class QueueUtil
|
7
|
+
# Constants
|
8
|
+
LOG_KEY = 'SFDC - QueueUtil'
|
9
|
+
SEPARATOR = ','
|
10
|
+
QUOTE_CHAR = '"'
|
11
|
+
|
12
|
+
# Zip up the tempfile, which is a CSV file, and the field types, so that when parsing the CSV file we can accurately
|
13
|
+
# convert each field to its respective type. Like Integers and Booleans.
|
14
|
+
EventLogFile = Struct.new(:field_types, :temp_file, :event_type)
|
15
|
+
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@logger = Cabin::Channel.get(LogStash)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# Given a list of query result's, iterate through it and grab the CSV file associated with it. Then parse the CSV file
|
25
|
+
# line by line and generating the event object for it. Then enqueue it.
|
26
|
+
|
27
|
+
public
|
28
|
+
def enqueue_events(query_result_list, queue, client)
|
29
|
+
@logger.info("#{LOG_KEY}: enqueue events")
|
30
|
+
|
31
|
+
# Grab a list of Tempfiles that contains CSV file data.
|
32
|
+
event_log_file_records = get_event_log_file_records(query_result_list, client)
|
33
|
+
|
34
|
+
# Iterate though each record.
|
35
|
+
event_log_file_records.each do |elf|
|
36
|
+
begin
|
37
|
+
# Create local variable to simplify & make code more readable.
|
38
|
+
tmp = elf.temp_file
|
39
|
+
|
40
|
+
# Get the schema from the first line in the tempfile. It will be in CSV format so we parse it, and it will
|
41
|
+
# return an array.
|
42
|
+
schema = CSV.parse_line(tmp.readline, col_sep: SEPARATOR, quote_char: QUOTE_CHAR)
|
43
|
+
|
44
|
+
# Loop through tempfile, line by line.
|
45
|
+
tmp.each_line do |line|
|
46
|
+
# Parse the current line, it will return an string array.
|
47
|
+
string_array = CSV.parse_line(line, col_sep: SEPARATOR, quote_char: QUOTE_CHAR)
|
48
|
+
|
49
|
+
# Convert the string array into its corresponding type array.
|
50
|
+
data = string_to_type_array(string_array, elf.field_types)
|
51
|
+
|
52
|
+
# create_event will return a event object.
|
53
|
+
queue << create_event(schema, data, elf.event_type)
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
# Close tmp file and unlink it, doing this will delete the actual tempfile.
|
57
|
+
tmp.close
|
58
|
+
tmp.unlink
|
59
|
+
end
|
60
|
+
end # do loop, tempfile_list
|
61
|
+
end # def create_event_list
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
# Convert the given string array to its corresponding type array and return it.
|
67
|
+
|
68
|
+
private
|
69
|
+
def string_to_type_array(string_array, field_types)
|
70
|
+
data = []
|
71
|
+
|
72
|
+
field_types.each_with_index do |type, i|
|
73
|
+
case type
|
74
|
+
when 'Number'
|
75
|
+
data[i] = (string_array[i].empty?) ? nil : string_array[i].to_f
|
76
|
+
when 'Boolean'
|
77
|
+
data[i] = (string_array[i].empty?) ? nil : (string_array[i] == '0')
|
78
|
+
when 'IP'
|
79
|
+
data[i] = valid_ip(string_array[i]) ? string_array[i] : nil
|
80
|
+
else # 'String', 'Id', 'EscapedString', 'Set'
|
81
|
+
data[i] = (string_array[i].empty?) ? nil : string_array[i]
|
82
|
+
end
|
83
|
+
end # do loop
|
84
|
+
|
85
|
+
data
|
86
|
+
end # convert_string_to_type
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
# Check if the given ip address is truely an ip address.
|
91
|
+
|
92
|
+
private
|
93
|
+
def valid_ip(ip)
|
94
|
+
ip =~ Resolv::IPv4::Regex ? true : false
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
# Bases on the schema and data, we create the event object. At any point if the data is nil we simply dont add
|
101
|
+
# the data to the event object. Special handling is needed when the schema 'TIMESTAMP' occurs, then the data
|
102
|
+
# associated with it needs to be converted into a LogStash::Timestamp.
|
103
|
+
|
104
|
+
private
|
105
|
+
def create_event(schema, data, event_type)
|
106
|
+
# Initaialize event to be used. @timestamp and @version is automatically added
|
107
|
+
event = LogStash::Event.new
|
108
|
+
|
109
|
+
# Add column data pair to event.
|
110
|
+
data.each_index do |i|
|
111
|
+
# Grab current key.
|
112
|
+
schema_name = schema[i]
|
113
|
+
|
114
|
+
# Handle when field_name is 'TIMESTAMP', Change the @timestamp field to the actual time on the CSV file,
|
115
|
+
# but convert it to iso8601.
|
116
|
+
if schema_name == 'TIMESTAMP'
|
117
|
+
epochmillis = DateTime.parse(data[i]).to_time.to_f
|
118
|
+
event.timestamp = LogStash::Timestamp.at(epochmillis)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Allow Elasticsearch index's have to types set to EventType.
|
122
|
+
event['type'] = event_type.downcase
|
123
|
+
|
124
|
+
# Add the schema data pair to event object.
|
125
|
+
if data[i] != nil
|
126
|
+
event[schema_name] = data[i]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return the event
|
131
|
+
event
|
132
|
+
end # def create_event
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
# This helper method takes as input a list/collection of SObjects which each contains a path to their respective CSV
|
138
|
+
# files. The path is stored in the LogFile field. Using that path, we are able to grab the actual CSV file via
|
139
|
+
# @client.http_get method.
|
140
|
+
#
|
141
|
+
# After grabbing the CSV file we then store them using the standard Tempfile library. Tempfile will create a unique
|
142
|
+
# file each time using 'sfdc_elf_tempfile' as the prefix and finally we will be returning a list of Tempfile object,
|
143
|
+
# where the user can read the Tempfile and then close it and unlink it, which will delete the file.
|
144
|
+
|
145
|
+
public
|
146
|
+
def get_event_log_file_records(query_result_list, client)
|
147
|
+
@logger.info("#{LOG_KEY}: generating tempfile list")
|
148
|
+
result = []
|
149
|
+
query_result_list.each do |event_log_file|
|
150
|
+
# Get the path of the CSV file from the LogFile field, then stream the data to the .write method of the Tempfile
|
151
|
+
tmp = Tempfile.new('sfdc_elf_tempfile')
|
152
|
+
client.streaming_download(event_log_file.LogFile, tmp)
|
153
|
+
|
154
|
+
# Flushing will write the buffer into the Tempfile itself.
|
155
|
+
tmp.flush
|
156
|
+
|
157
|
+
# Rewind will move the file pointer from the end to the beginning of the file, so that users can simple
|
158
|
+
# call the Read method.
|
159
|
+
tmp.rewind
|
160
|
+
|
161
|
+
# Append the EventLogFile object into the result list
|
162
|
+
field_types = event_log_file.LogFileFieldTypes.split(',')
|
163
|
+
result << EventLogFile.new(field_types, tmp, event_log_file.EventType)
|
164
|
+
|
165
|
+
# Log the info from event_log_file object.
|
166
|
+
@logger.info(" #{LOG_KEY}: Id = #{event_log_file.Id}")
|
167
|
+
@logger.info(" #{LOG_KEY}: EventType = #{event_log_file.EventType}")
|
168
|
+
@logger.info(" #{LOG_KEY}: LogFile = #{event_log_file.LogFile}")
|
169
|
+
@logger.info(" #{LOG_KEY}: LogDate = #{event_log_file.LogDate}")
|
170
|
+
@logger.info(" #{LOG_KEY}: LogFileLength = #{event_log_file.LogFileLength}")
|
171
|
+
@logger.info(" #{LOG_KEY}: LogFileFieldTypes = #{event_log_file.LogFileFieldTypes}")
|
172
|
+
@logger.info(' ......................................')
|
173
|
+
end
|
174
|
+
result
|
175
|
+
end # def get_event_log_file_records
|
176
|
+
end # QueueUtil
|