logstash-input-salesforce 3.2.1 → 3.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 991a872d0042a97aeb4f9d7d27d6cc25fad15a62771a8a6c6b7eae9d427d55a8
4
- data.tar.gz: 525f6d7d6796b4670dcd47faabc7dbbbd641355b763626693e5be7edfc059107
3
+ metadata.gz: 9c35a5c5144e60a5958fefebef36dfe5eb74a9b833e56dc513639f4e56d8e476
4
+ data.tar.gz: ff39efdf9da888ad2668e91fa1afcf7ac8c99ebb56579f675b9efa27387154fa
5
5
  SHA512:
6
- metadata.gz: aca114e8b09b4895136cbd398d5134e7ef2adf85d1a2e067fe7a5986940f7ec1bdf27293b07da43c95db946b873bd8cbabd30d5e2c62550bb96775f9bd15d0a2
7
- data.tar.gz: b5f752607e86f8b9873d3c8e45477dd24007d5f97cbbd3305645608fcf185d088a7f478f7175cd4f288495f6d61e184aa038a75e1f0919c19e6516756e6e17eb
6
+ metadata.gz: 3237d6346f270425502e9d1ec134b976bbb750149ecc2c6814647ae19a4e5a14f9ad9f59e201ce2c8fa354230ab4e848a6d16872ee9cb6df502f79b9ab39d58e
7
+ data.tar.gz: ed6170f0cb50898d9431f9eacc1d5f6661785dfcde4877ffdc3ead944e1bff15846274bdf60af0e89dbda8704dd59a3a4297fe5ae8c5725f7515c94fc0ca71e1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 3.3.0
2
+ - Added `timeout` configuration to override the connect/read timeout for REST requests to Salesforce
3
+ - Added `interval` configuration to run the plugin continuously, querying records and publishing events
4
+ for them at a set interval instead of running once and quitting
5
+ - Added incremental data loading feature, controlled by configuration options `tracking_field`, `tracking_field_value_file`, and `changed_data_filter`
6
+
1
7
  ## 3.2.1
2
8
  - Changes sensitive configs type to Password for better protection from leaks in debug logs. [#35](https://github.com/logstash-plugins/logstash-input-salesforce/pull/35)
3
9
 
data/README.md CHANGED
@@ -1,9 +1,34 @@
1
- # Logstash Plugin
1
+ # Logstash Salesforce input Plugin
2
+
3
+ This Logstash input plugin allows you to query Salesforce using SOQL and puts the results
4
+ into Logstash, one row per event. You can configure it to pull entire sObjects or only
5
+ specific fields.
2
6
 
3
7
  This is a plugin for [Logstash](https://github.com/elasticsearch/logstash).
4
8
 
5
9
  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
10
 
11
+ ## How to use
12
+
13
+ Add the input plugin to your Logstash pipeline definition.
14
+
15
+ This example queries all the Salesforce Opportunities and publishes an event for each opportunity found:
16
+
17
+ ```ruby
18
+ input {
19
+ salesforce {
20
+ client_id => 'OAUTH CLIENT ID FROM YOUR SFDC APP'
21
+ client_secret => 'OAUTH CLIENT SECRET FROM YOUR SFDC APP'
22
+ username => 'email@example.com'
23
+ password => 'super-secret'
24
+ security_token => 'SECURITY TOKEN FOR THIS USER'
25
+ sfdc_object_name => 'Opportunity'
26
+ }
27
+ }
28
+ ```
29
+
30
+ For more examples and an explanation of all configuration options, see https://www.elastic.co/docs/reference/logstash/plugins/plugins-inputs-salesforce.
31
+
7
32
  ## Documentation
8
33
 
9
34
  Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elasticsearch.org/guide/en/logstash/current/).
@@ -17,39 +42,55 @@ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/log
17
42
 
18
43
  ## Developing
19
44
 
20
- ### 1. Plugin Developement and Testing
45
+ ### 1. Plugin Development and Testing
21
46
 
22
47
  #### Code
23
- - To get started, you'll need JRuby with the Bundler gem installed.
24
48
 
25
- - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
49
+ - To get started, you'll need JRuby with the Bundler gem installed. We strongly recommend
50
+ using a Ruby Version Manager such as `rvm` to install JRuby. If you're using a JRuby installed
51
+ by Homebrew on macOS, replace the `bundle` command with `jbundle` in all examples in this
52
+ document: Homebrew renames the JRuby binaries so that they don't clash with those from the system
53
+ (C) Ruby that ships with macOS.
54
+
55
+ - Clone the plugin code from the GitHub [logstash-plugins/logstash-input-salesforce](https://github.com/logstash-plugins/logstash-input-salesforce) repository.
26
56
 
27
57
  - Install dependencies
28
58
  ```sh
29
- bundle install
59
+ bundle install --path=vendor/bundle
30
60
  ```
31
61
 
62
+ - Download a source release of the Logstash version you're targeting
63
+ (e.g. https://github.com/elastic/logstash/archive/refs/tags/v8.14.3.zip) and
64
+ extract (unzip) it to a local directory.
65
+
32
66
  #### Test
33
67
 
34
68
  - Update your dependencies
35
69
 
36
70
  ```sh
37
- bundle install
71
+ bundle install --path=vendor/bundle
38
72
  ```
39
73
 
40
74
  - Run tests
41
75
 
42
76
  ```sh
77
+ export LOGSTASH_PATH=<path to logstash source>
78
+ export LOGSTASH_SOURCE=1
43
79
  bundle exec rspec
44
80
  ```
45
81
 
82
+ If you get an error like `Could not find logstash-core-plugin-api-2.1.16-java, logstash-core-8.14.3-java in locally
83
+ installed gems`, double check that you've set and exported the `LOGSTASH_PATH` and `LOGSTASH_SOURCE` environment
84
+ variables as explained in the previous section, and that the `LOGSTASH_PATH` points to an unzipped Logstash source
85
+ distribution.
86
+
46
87
  ### 2. Running your unpublished Plugin in Logstash
47
88
 
48
89
  #### 2.1 Run in a local Logstash clone
49
90
 
50
91
  - Edit Logstash `Gemfile` and add the local plugin path, for example:
51
92
  ```ruby
52
- gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
93
+ gem "logstash-input-salesforce", :path => "/your/local/logstash-input-salesforce"
53
94
  ```
54
95
  - Install plugin
55
96
  ```sh
@@ -62,7 +103,7 @@ bin/plugin install --no-verify
62
103
  ```
63
104
  - Run Logstash with your plugin
64
105
  ```sh
65
- bin/logstash -e 'filter {awesome {}}'
106
+ bin/logstash -e 'input { salesforce { ... } }'
66
107
  ```
67
108
  At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
68
109
 
@@ -72,7 +113,7 @@ You can use the same **2.1** method to run your plugin in an installed Logstash
72
113
 
73
114
  - Build your plugin gem
74
115
  ```sh
75
- gem build logstash-filter-awesome.gemspec
116
+ gem build logstash-input-salesforce.gemspec
76
117
  ```
77
118
  - Install the plugin from the Logstash home
78
119
  ```sh
data/docs/index.asciidoc CHANGED
@@ -25,14 +25,15 @@ This Logstash input plugin allows you to query Salesforce using SOQL and puts th
25
25
  into Logstash, one row per event. You can configure it to pull entire sObjects or only
26
26
  specific fields.
27
27
 
28
- NOTE: This input plugin will stop after all the results of the query are processed and will
29
- need to be re-run to fetch new results. It does not utilize the streaming API.
28
+ NOTE: By default, this input plugin will stop after all the results of the query are processed and will
29
+ need to be re-run to fetch new results. It does not utilize the streaming API. However, by setting the `interval`
30
+ configuration option you can configure the plugin to automatically run at a set frequency.
30
31
 
31
- In order to use this plugin, you will need to create a new SFDC Application using
32
- oauth. More details can be found here:
32
+ In order to use this plugin, you will need to create a new Salesforce Connected App with OAuth enabled.
33
+ More details can be found here:
33
34
  https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm
34
35
 
35
- You will also need a username, password, and security token for your salesforce instance.
36
+ You will also need a username, password, and security token for your Salesforce instance.
36
37
  More details for generating a token can be found here:
37
38
  https://help.salesforce.com/apex/HTViewHelpDoc?id=user_security_token.htm
38
39
 
@@ -41,7 +42,7 @@ that will be used in the SOQL query.
41
42
 
42
43
  ==== HTTP proxy
43
44
 
44
- If your infrastructure uses a HTTP proxy, you can set the `SALESFORCE_PROXY_URI` environment variable with the desired URI value (e.g `export SALESFORCE_PROXY_URI="http://proxy.example.com:123"`).
45
+ If your infrastructure uses an HTTP proxy, you can set the `SALESFORCE_PROXY_URI` environment variable with the desired URI value (e.g `export SALESFORCE_PROXY_URI="http://proxy.example.com:123"`).
45
46
 
46
47
  ==== Example
47
48
  This example prints all the Salesforce Opportunities to standard out
@@ -75,15 +76,20 @@ This plugin supports the following configuration options plus the <<plugins-{typ
75
76
  |=======================================================================
76
77
  |Setting |Input type|Required
77
78
  | <<plugins-{type}s-{plugin}-api_version>> |<<string,string>>|No
79
+ | <<plugins-{type}s-{plugin}-changed_data_filter>> |<<string,string>>|No
78
80
  | <<plugins-{type}s-{plugin}-client_id>> |<<string,string>>|Yes
79
81
  | <<plugins-{type}s-{plugin}-client_secret>> |<<password,password>>|Yes
82
+ | <<plugins-{type}s-{plugin}-interval>> |<<number,number>>|No
80
83
  | <<plugins-{type}s-{plugin}-password>> |<<password,password>>|Yes
81
84
  | <<plugins-{type}s-{plugin}-security_token>> |<<password,password>>|Yes
82
85
  | <<plugins-{type}s-{plugin}-sfdc_fields>> |<<array,array>>|No
83
86
  | <<plugins-{type}s-{plugin}-sfdc_filters>> |<<string,string>>|No
84
87
  | <<plugins-{type}s-{plugin}-sfdc_instance_url>> |<<string,string>>|No
85
88
  | <<plugins-{type}s-{plugin}-sfdc_object_name>> |<<string,string>>|Yes
89
+ | <<plugins-{type}s-{plugin}-timeout>> |<<number,number>>|No
86
90
  | <<plugins-{type}s-{plugin}-to_underscores>> |<<boolean,boolean>>|No
91
+ | <<plugins-{type}s-{plugin}-tracking_field>> |<<string,string>>|No
92
+ | <<plugins-{type}s-{plugin}-tracking_field_value_file>> |<<string,string>>|No
87
93
  | <<plugins-{type}s-{plugin}-use_test_sandbox>> |<<boolean,boolean>>|No
88
94
  | <<plugins-{type}s-{plugin}-use_tooling_api>> |<<boolean,boolean>>|No
89
95
  | <<plugins-{type}s-{plugin}-username>> |<<string,string>>|Yes
@@ -101,7 +107,32 @@ input plugins.
101
107
  * There is no default value for this setting.
102
108
 
103
109
  By default, this uses the default Restforce API version.
104
- To override this, set this to something like "32.0" for example
110
+ To override this, set this to something like "32.0" for example.
111
+
112
+ [id="plugins-{type}s-{plugin}-changed_data_filter"]
113
+ ===== `changed_data_filter`
114
+
115
+ * Value type is <<string,string>>
116
+ * There is no default value for this setting.
117
+
118
+ The filter to add to the Salesforce query when a previous tracking field value
119
+ was read from the <<plugins-{type}s-{plugin}-tracking_field_value_file>>.
120
+ The string can (and should) contain a placeholder `%+{last_tracking_field_value}+` that
121
+ will be substituted with the actual value read from the <<plugins-{type}s-{plugin}-tracking_field_value_file>>.
122
+
123
+ This clause is combined with any <<plugins-{type}s-{plugin}-sfdc_filters>>
124
+ clause that is configured using the `AND` operator.
125
+
126
+ The value should be properly quoted according to the SOQL rules for the field
127
+ type.
128
+
129
+ **Examples:**
130
+
131
+ [source,ruby]
132
+ "changed_data_filter" => "Number > '%{last_tracking_field_value}'"
133
+
134
+ [source,ruby]
135
+ "changed_data_filter" => "LastModifiedDate >= %{last_tracking_field_value}"
105
136
 
106
137
  [id="plugins-{type}s-{plugin}-client_id"]
107
138
  ===== `client_id`
@@ -110,10 +141,10 @@ To override this, set this to something like "32.0" for example
110
141
  * Value type is <<string,string>>
111
142
  * There is no default value for this setting.
112
143
 
113
- Consumer Key for authentication. You must set up a new SFDC
114
- connected app with oath to use this output. More information
144
+ Consumer Key for authentication. You must set up a new Salesforce
145
+ connected app with OAuth enabled to use this plugin. More information
115
146
  can be found here:
116
- https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm
147
+ https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm.
117
148
 
118
149
  [id="plugins-{type}s-{plugin}-client_secret"]
119
150
  ===== `client_secret`
@@ -122,30 +153,47 @@ https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm
122
153
  * Value type is <<password,password>>
123
154
  * There is no default value for this setting.
124
155
 
125
- Consumer Secret from your oauth enabled connected app
156
+ Consumer secret from your OAuth enabled connected app.
157
+
158
+ [id="plugins-{type}s-{plugin}-interval"]
159
+ ===== `interval`
160
+
161
+ * Value type is <<number,number>>
162
+ * There is no default value for this setting.
163
+
164
+ The interval in seconds between each run of the plugin.
165
+
166
+ If specified, the plugin only terminates when it receives the stop
167
+ signal from Logstash, e.g. when you press Ctrl-C when running interactively,
168
+ or when the process receives a TERM signal. It will query and publish
169
+ events for all results, then sleep until `interval` seconds from the start
170
+ of the previous run of the plugin have passed. If the plugin ran for longer
171
+ than `interval` seconds, it will run again immediately.
172
+
173
+ If this property is not specified or is set to -1, the plugin will run once and then exit.
126
174
 
127
175
  [id="plugins-{type}s-{plugin}-password"]
128
- ===== `password`
176
+ ===== `password`
129
177
 
130
178
  * This is a required setting.
131
179
  * Value type is <<password,password>>
132
180
  * There is no default value for this setting.
133
181
 
134
- The password used to login to sfdc
182
+ The password used to log in to Salesforce.
135
183
 
136
184
  [id="plugins-{type}s-{plugin}-security_token"]
137
- ===== `security_token`
185
+ ===== `security_token`
138
186
 
139
- * This is a required setting.
140
- * Value type is <<password,password>>
141
- * There is no default value for this setting.
187
+ * This is a required setting.
188
+ * Value type is <<password,password>>
189
+ * There is no default value for this setting.
142
190
 
143
191
  The security token for this account. For more information about
144
- generting a security token, see:
145
- https://help.salesforce.com/apex/HTViewHelpDoc?id=user_security_token.htm
192
+ generating a security token, see:
193
+ https://help.salesforce.com/apex/HTViewHelpDoc?id=user_security_token.htm.
146
194
 
147
195
  [id="plugins-{type}s-{plugin}-sfdc_fields"]
148
- ===== `sfdc_fields`
196
+ ===== `sfdc_fields`
149
197
 
150
198
  * Value type is <<array,array>>
151
199
  * Default value is `[]`
@@ -159,9 +207,9 @@ If this is empty, all fields are returned.
159
207
  * Value type is <<string,string>>
160
208
  * Default value is `""`
161
209
 
162
- These options will be added to the WHERE clause in the
210
+ These options will be added to the `WHERE` clause in the
163
211
  SOQL statement. Additional fields can be filtered on by
164
- adding field1 = value1 AND field2 = value2 AND...
212
+ adding `field1 = value1 AND field2 = value2 AND...`.
165
213
 
166
214
  [id="plugins-{type}s-{plugin}-sfdc_instance_url"]
167
215
  ===== `sfdc_instance_url`
@@ -183,7 +231,18 @@ but not both to configure the url to which the plugin connects to.
183
231
  * Value type is <<string,string>>
184
232
  * There is no default value for this setting.
185
233
 
186
- The name of the salesforce object you are creating or updating
234
+ The name of the Salesforce object you are creating or updating.
235
+
236
+ [id="plugins-{type}s-{plugin}-timeout"]
237
+ ===== `timeout`
238
+
239
+ * Value type is <<number,number>>
240
+ * Default value is `60`
241
+
242
+ The timeout to apply to REST API calls to Salesforce, in seconds. If
243
+ a connection to Salesforce cannot be made in this time, an error occurs.
244
+ If it takes longer than the timeout for a block of data (e.g. query results) to be
245
+ read, an error occurs.
187
246
 
188
247
  [id="plugins-{type}s-{plugin}-to_underscores"]
189
248
  ===== `to_underscores`
@@ -191,7 +250,59 @@ The name of the salesforce object you are creating or updating
191
250
  * Value type is <<boolean,boolean>>
192
251
  * Default value is `false`
193
252
 
194
- Setting this to true will convert SFDC's NamedFields__c to named_fields__c
253
+ Setting this to true will convert Salesforce's `++NamedFields__c++` to `++named_fields__c++`.
254
+
255
+ [id="plugins-{type}s-{plugin}-tracking_field"]
256
+ ===== `tracking_field`
257
+
258
+ * Value type is <<string,string>>
259
+ * There is no default value for this setting.
260
+
261
+ The field to track for incremental data loads. This field will
262
+ be used in an `ORDER BY ... ASC` clause that is added to the Salesforce query.
263
+ This field _should_ also be used in the <<plugins-{type}s-{plugin}-changed_data_filter>> clause
264
+ to actually achieve incremental loading of data.
265
+
266
+ The last value (which is the highest value if the query sorts by this field ascending)
267
+ value for this field will be saved to the file at the path configured by
268
+ <<plugins-{type}s-{plugin}-tracking_field_value_file>>, if specified.
269
+
270
+ This field should ideally be strictly ascending for new records. An
271
+ autonumber field is ideal for this.
272
+
273
+ The standard `LastModifiedDate` field can be used, but since it is not _strictly_
274
+ ascending (multiple records can have the same `LastModifiedDate`, the
275
+ <<plugins-{type}s-{plugin}-changed_data_filter>> should account for this by using the `>=`
276
+ operator, and duplicates should be expected.
277
+
278
+ Note that Salesforce does not guarantee that the standard `Id` field has ascending
279
+ values for new records (https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_best_practices.htm).
280
+ Therefore, using `Id` as tracking field risks missing records and is not recommended.
281
+
282
+ If this field is not already included in the <<plugins-{type}s-{plugin}-sfdc_fields>>,
283
+ it is added.
284
+
285
+ [id="plugins-{type}s-{plugin}-tracking_field_value_file"]
286
+ ===== `tracking_field_value_file`
287
+
288
+ * Value type is <<string,string>>
289
+ * There is no default value for this setting.
290
+
291
+ The full path to the file from which the latest tracking field value from the previous
292
+ plugin invocation will be read, and to which the new latest tracking field value will be
293
+ written after the current plugin invocation.
294
+
295
+ This keeps persistent track of the last seen value of the tracking field used for incremental
296
+ loading of data.
297
+
298
+ The file should be readable and writable by the Logstash process.
299
+
300
+ If the file exists and a <<plugins-{type}s-{plugin}-changed_data_filter>> is configured,
301
+ a changed data filter clause is added to the query (and combined with any <<plugins-{type}s-{plugin}-sfdc_filters>>
302
+ clause that is configured using the `AND` operator).
303
+
304
+ If the result set is not empty, the value for `tracking_field` from the last row is
305
+ written to the file.
195
306
 
196
307
  [id="plugins-{type}s-{plugin}-use_test_sandbox"]
197
308
  ===== `use_test_sandbox`
@@ -200,7 +311,7 @@ Setting this to true will convert SFDC's NamedFields__c to named_fields__c
200
311
  * Default value is `false`
201
312
 
202
313
  Set this to true to connect to a sandbox sfdc instance
203
- logging in through test.salesforce.com
314
+ logging in through test.salesforce.com.
204
315
 
205
316
  Use either this or the `sfdc_instance_url` configuration option
206
317
  but not both to configure the url to which the plugin connects to.
@@ -225,9 +336,9 @@ of elements of sfdc flows) and security health check risks.
225
336
  * Value type is <<string,string>>
226
337
  * There is no default value for this setting.
227
338
 
228
- A valid salesforce user name, usually your email address.
339
+ A valid Salesforce username, usually your email address.
229
340
  Used for authentication and will be the user all objects
230
- are created or modified by
341
+ are created or modified by.
231
342
 
232
343
 
233
344
 
@@ -2,6 +2,7 @@
2
2
  require "logstash/inputs/base"
3
3
  require "logstash/namespace"
4
4
  require "time"
5
+ require "stud/interval"
5
6
 
6
7
  # This Logstash input plugin allows you to query Salesforce using SOQL and puts the results
7
8
  # into Logstash, one row per event. You can configure it to pull entire sObjects or only
@@ -55,46 +56,84 @@ class LogStash::Inputs::Salesforce < LogStash::Inputs::Base
55
56
  # See https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling
56
57
  # for more details about the Tooling API
57
58
  config :use_tooling_api, :validate => :boolean, :default => false
59
+
58
60
  # Set this to true to connect to a sandbox sfdc instance
59
61
  # logging in through test.salesforce.com
60
62
  config :use_test_sandbox, :validate => :boolean, :default => false
63
+
61
64
  # Set this to the instance url of the sfdc instance you want
62
65
  # to connect to already during login. If you have configured
63
66
  # a MyDomain in your sfdc instance you would provide
64
67
  # <mydomain>.my.salesforce.com here.
65
68
  config :sfdc_instance_url, :validate => :string, :required => false
69
+
66
70
  # By default, this uses the default Restforce API version.
67
71
  # To override this, set this to something like "32.0" for example
68
72
  config :api_version, :validate => :string, :required => false
73
+
69
74
  # Consumer Key for authentication. You must set up a new SFDC
70
75
  # connected app with oath to use this output. More information
71
76
  # can be found here:
72
77
  # https://help.salesforce.com/apex/HTViewHelpDoc?id=connected_app_create.htm
73
78
  config :client_id, :validate => :string, :required => true
79
+
74
80
  # Consumer Secret from your oauth enabled connected app
75
81
  config :client_secret, :validate => :password, :required => true
82
+
76
83
  # A valid salesforce user name, usually your email address.
77
84
  # Used for authentication and will be the user all objects
78
85
  # are created or modified by
79
86
  config :username, :validate => :string, :required => true
87
+
80
88
  # The password used to login to sfdc
81
89
  config :password, :validate => :password, :required => true
90
+
82
91
  # The security token for this account. For more information about
83
92
  # generting a security token, see:
84
93
  # https://help.salesforce.com/apex/HTViewHelpDoc?id=user_security_token.htm
85
94
  config :security_token, :validate => :password, :required => true
95
+
86
96
  # The name of the salesforce object you are creating or updating
87
97
  config :sfdc_object_name, :validate => :string, :required => true
98
+
88
99
  # These are the field names to return in the Salesforce query
89
100
  # If this is empty, all fields are returned.
90
101
  config :sfdc_fields, :validate => :array, :default => []
102
+
91
103
  # These options will be added to the WHERE clause in the
92
104
  # SOQL statement. Additional fields can be filtered on by
93
105
  # adding field1 = value1 AND field2 = value2 AND...
94
106
  config :sfdc_filters, :validate => :string, :default => ""
107
+
108
+ # RESTForce request timeout in seconds.
109
+ config :timeout, :validate => :number, :default => 60, :required => false
110
+
95
111
  # Setting this to true will convert SFDC's NamedFields__c to named_fields__c
96
112
  config :to_underscores, :validate => :boolean, :default => false
97
113
 
114
+ # File that stores the tracking field's latest value. This is read before querying data to interpolate
115
+ # the tracking field value into the incremental_filter, and the latest value of the tracking field is written
116
+ # to it after all the query results have been read.
117
+ config :tracking_field_value_file, :validate => :string, :required => false
118
+
119
+ # Filter clause to use for incremental retrieval and indexing of data that has changed since the last invodation
120
+ # of the plugin. This is combined with sfdc_filters using the AND operator, if tracking_field_value_path exists.
121
+ # String interpolation is applied to replace "%{last_tracking_field_value}" in this string with the value read
122
+ # from tracking_field_value_file. This would usually be something like "tracking_field > '%{last_tracking_field_value}'"
123
+ # where tracking_field is the API name of the actual tracking field set using the tracking_field configuration property,
124
+ # e.g. LastModifiedDate
125
+ config :changed_data_filter, :validate => :string, :required => false
126
+
127
+ # The field from which the last value will be stored in the tracking_field_value_file and interpolated
128
+ # for "%{last_tracking_field_value}" in the changed_data_filter expression. This field will also be used in an ORDER BY
129
+ # clause added to the query, with sorting done ascending, so that the last value in the results is also the
130
+ # highest.
131
+ config :tracking_field, :validate => :string, :required => false
132
+
133
+ # Interval to run the command. Value is in seconds. If no interval is given,
134
+ # this plugin only fetches data once.
135
+ config :interval, :validate => :number, :required => false, :default => -1
136
+
98
137
  public
99
138
  def register
100
139
  require 'restforce'
@@ -105,26 +144,57 @@ class LogStash::Inputs::Salesforce < LogStash::Inputs::Base
105
144
 
106
145
  public
107
146
  def run(queue)
108
- results = client.query(get_query())
109
- if results && results.first
110
- results.each do |result|
111
- event = LogStash::Event.new()
112
- decorate(event)
113
- @sfdc_fields.each do |field|
114
- field_type = @sfdc_field_types[field]
115
- value = result.send(field)
116
- event_key = @to_underscores ? underscore(field) : field
117
- if not value.nil?
118
- case field_type
119
- when 'datetime', 'date'
120
- event.set(event_key, format_time(value))
121
- else
122
- event.set(event_key, value)
147
+ while !stop?
148
+ start = Time.now
149
+ results = client.query(get_query())
150
+ latest_tracking_field_value = nil
151
+ if results && results.first
152
+ results.each do |result|
153
+ event = LogStash::Event.new()
154
+ decorate(event)
155
+ @sfdc_fields.each do |field|
156
+ field_type = @sfdc_field_types[field]
157
+ value = result.send(field)
158
+ event_key = @to_underscores ? underscore(field) : field
159
+ unless value.nil?
160
+ case field_type
161
+ when 'datetime', 'date'
162
+ event.set(event_key, format_time(value))
163
+ else
164
+ event.set(event_key, value)
165
+ end
123
166
  end
124
167
  end
168
+ queue << event
169
+ unless @tracking_field.nil?
170
+ latest_tracking_field_value = result[@tracking_field]
171
+ end
172
+ end # loop sObjects
173
+ end
174
+
175
+ unless @tracking_field_value_file.nil?
176
+ unless latest_tracking_field_value.nil?
177
+ @logger.debug("Writing latest tracking field value " + latest_tracking_field_value + " to " + @tracking_field_value_file)
178
+ File.write(@tracking_field_value_file, latest_tracking_field_value)
179
+ else
180
+ @logger.debug("No tracking field value found in result, not updating " + @tracking_field_value_file)
125
181
  end
126
- queue << event
127
182
  end
183
+
184
+ if @interval == -1
185
+ break
186
+ else
187
+ duration = Time.now - start
188
+ # Sleep for the remainder of the interval, or 0 if the duration ran
189
+ # longer than the interval.
190
+ sleeptime = [0, @interval - duration].max
191
+ if sleeptime == 0
192
+ @logger.warn("Execution ran longer than the interval. Skipping sleep.",
193
+ :duration => duration,
194
+ :interval => @interval)
195
+ end
196
+ Stud.stoppable_sleep(sleeptime) { stop? }
197
+ end # end interval check
128
198
  end
129
199
  end # def run
130
200
 
@@ -144,7 +214,8 @@ class LogStash::Inputs::Salesforce < LogStash::Inputs::Base
144
214
  :password => @password.value,
145
215
  :security_token => @security_token.value,
146
216
  :client_id => @client_id,
147
- :client_secret => @client_secret.value
217
+ :client_secret => @client_secret.value,
218
+ :timeout => @timeout
148
219
  }
149
220
  # configure the endpoint to which restforce connects to for authentication
150
221
  if @sfdc_instance_url && @use_test_sandbox
@@ -160,15 +231,43 @@ class LogStash::Inputs::Salesforce < LogStash::Inputs::Base
160
231
 
161
232
  private
162
233
  def get_query()
163
- query = ["SELECT",@sfdc_fields.join(','),
164
- "FROM",@sfdc_object_name]
165
- query << ["WHERE",@sfdc_filters] unless @sfdc_filters.empty?
166
- query << "ORDER BY LastModifiedDate DESC" if @sfdc_fields.include?('LastModifiedDate')
234
+ sfdc_fields = @sfdc_fields.dup
235
+ unless @tracking_field.nil?
236
+ unless sfdc_fields.include?(@tracking_field)
237
+ sfdc_fields << [@tracking_field]
238
+ end
239
+ end
240
+ query = ["SELECT", sfdc_fields.join(','),
241
+ "FROM", @sfdc_object_name]
242
+ where = []
243
+ unless @sfdc_filters.empty?
244
+ append_to_where_clause(@sfdc_filters, where)
245
+ end
246
+ unless @changed_data_filter.nil?
247
+ if File.exist?(@tracking_field_value_file)
248
+ last_tracking_field_value = File.read(@tracking_field_value_file)
249
+ changed_data_filter_interpolated = @changed_data_filter % { :last_tracking_field_value => last_tracking_field_value }
250
+ append_to_where_clause(changed_data_filter_interpolated, where)
251
+ end
252
+ end
253
+ query << where
254
+ unless @tracking_field.nil?
255
+ query << ["ORDER BY", @tracking_field, "ASC"]
256
+ end
167
257
  query_str = query.flatten.join(" ")
168
258
  @logger.debug? && @logger.debug("SFDC Query", :query => query_str)
169
259
  return query_str
170
260
  end
171
261
 
262
+ def append_to_where_clause(changed_data_filter_interpolated, where)
263
+ if where.empty?
264
+ where << ["WHERE"]
265
+ else
266
+ where << ["AND"]
267
+ end
268
+ where << [changed_data_filter_interpolated]
269
+ end
270
+
172
271
  private
173
272
  def get_field_types(obj_desc)
174
273
  field_types = {}
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-salesforce'
3
- s.version = '3.2.1'
4
- s.licenses = ['Apache License (2.0)']
3
+ s.version = '3.3.0'
4
+ s.licenses = ['apache-2.0']
5
5
  s.summary = "Creates events based on a Salesforce SOQL query"
6
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
7
  s.authors = ["Russ Savage"]