logstash-filter-javascript 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +2 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +15 -0
- data/LICENSE +201 -0
- data/README.md +165 -0
- data/Rakefile +7 -0
- data/lib/logstash/filters/javascript.rb +231 -0
- data/logstash-filter-javascript.gemspec +31 -0
- data/spec/filters/javascript_spec.rb +245 -0
- data/spec/fixtures/field_multiplier.js +30 -0
- data/spec/fixtures/ramda.js +9716 -0
- data/spec/fixtures/throwIfErrorFieldSet.js +6 -0
- data/spec/fixtures/underscore.js +1939 -0
- data/spec/spec_helper.rb +2 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29f052a4a01e745f861356cc0c05cb1dada4b32b8629238729ef701cbc0bd053
|
4
|
+
data.tar.gz: 5d3d25dbd4bdaecb69e3e2bffbb52923802378bf680633c5c60c201304dc807d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4423ee555a828ade3a9edb57e833033c19986a34b695619fe831c29056c972f13c182d34f6b1d31a598692156876583bb3dbd4d26d70affaef37befb3dc2750
|
7
|
+
data.tar.gz: c68529222ee112d050d980135fe6a1a53841466494d78597508b24851432eb05b9c3471f397486cb621fa3ce9dcb1da189936b3ca2c4024dbb1f10f9c4031fdb
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
logstash_path = ENV["LOGSTASH_PATH"] || "../../logstash"
|
6
|
+
use_logstash_source = (ENV["LOGSTASH_SOURCE"] || '1').to_s == "1"
|
7
|
+
|
8
|
+
if Dir.exist?(logstash_path) && use_logstash_source
|
9
|
+
gem 'logstash-core', :path => "#{logstash_path}/logstash-core"
|
10
|
+
gem 'logstash-core-plugin-api', :path => "#{logstash_path}/logstash-core-plugin-api"
|
11
|
+
end
|
12
|
+
|
13
|
+
group(:test) do
|
14
|
+
gem 'logstash-filter-ruby', require: false
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
Apache License
|
2
|
+
Version 2.0, January 2004
|
3
|
+
http://www.apache.org/licenses/
|
4
|
+
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6
|
+
|
7
|
+
1. Definitions.
|
8
|
+
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
11
|
+
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13
|
+
the copyright owner that is granting the License.
|
14
|
+
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
16
|
+
other entities that control, are controlled by, or are under common
|
17
|
+
control with that entity. For the purposes of this definition,
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
19
|
+
direction or management of such entity, whether by contract or
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22
|
+
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24
|
+
exercising permissions granted by this License.
|
25
|
+
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
27
|
+
including but not limited to software source code, documentation
|
28
|
+
source, and configuration files.
|
29
|
+
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
31
|
+
transformation or translation of a Source form, including but
|
32
|
+
not limited to compiled object code, generated documentation,
|
33
|
+
and conversions to other media types.
|
34
|
+
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
36
|
+
Object form, made available under the License, as indicated by a
|
37
|
+
copyright notice that is included in or attached to the work
|
38
|
+
(an example is provided in the Appendix below).
|
39
|
+
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46
|
+
the Work and Derivative Works thereof.
|
47
|
+
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
49
|
+
the original version of the Work and any modifications or additions
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
61
|
+
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
64
|
+
subsequently incorporated within the Work.
|
65
|
+
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
72
|
+
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78
|
+
where such license applies only to those patent claims licensable
|
79
|
+
by such Contributor that are necessarily infringed by their
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
82
|
+
institute patent litigation against any entity (including a
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
85
|
+
or contributory patent infringement, then any patent licenses
|
86
|
+
granted to You under this License for that Work shall terminate
|
87
|
+
as of the date such litigation is filed.
|
88
|
+
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
91
|
+
modifications, and in Source or Object form, provided that You
|
92
|
+
meet the following conditions:
|
93
|
+
|
94
|
+
(a) You must give any other recipients of the Work or
|
95
|
+
Derivative Works a copy of this License; and
|
96
|
+
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
98
|
+
stating that You changed the files; and
|
99
|
+
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
102
|
+
attribution notices from the Source form of the Work,
|
103
|
+
excluding those notices that do not pertain to any part of
|
104
|
+
the Derivative Works; and
|
105
|
+
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
108
|
+
include a readable copy of the attribution notices contained
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
111
|
+
of the following places: within a NOTICE text file distributed
|
112
|
+
as part of the Derivative Works; within the Source form or
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
114
|
+
within a display generated by the Derivative Works, if and
|
115
|
+
wherever such third-party notices normally appear. The contents
|
116
|
+
of the NOTICE file are for informational purposes only and
|
117
|
+
do not modify the License. You may add Your own attribution
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
120
|
+
that such additional attribution notices cannot be construed
|
121
|
+
as modifying the License.
|
122
|
+
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
124
|
+
may provide additional or different license terms and conditions
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
128
|
+
the conditions stated in this License.
|
129
|
+
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
133
|
+
this License, without any additional terms or conditions.
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135
|
+
the terms of any separate license agreement you may have executed
|
136
|
+
with Licensor regarding such Contributions.
|
137
|
+
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
140
|
+
except as required for reasonable and customary use in describing the
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
142
|
+
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
152
|
+
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
158
|
+
incidental, or consequential damages of any character arising as a
|
159
|
+
result of this License or out of the use or inability to use the
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
162
|
+
other commercial damages or losses), even if such Contributor
|
163
|
+
has been advised of the possibility of such damages.
|
164
|
+
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168
|
+
or other liability obligations and/or rights consistent with this
|
169
|
+
License. However, in accepting such obligations, You may act only
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
174
|
+
of your accepting any such warranty or additional liability.
|
175
|
+
|
176
|
+
END OF TERMS AND CONDITIONS
|
177
|
+
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
179
|
+
|
180
|
+
To apply the Apache License to your work, attach the following
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182
|
+
replaced with your own identifying information. (Don't include
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
184
|
+
comment syntax for the file format. We also recommend that a
|
185
|
+
file or class name and description of purpose be included on the
|
186
|
+
same "printed page" as the copyright notice for easier
|
187
|
+
identification within third-party archives.
|
188
|
+
|
189
|
+
Copyright [2020-3000] [Karol Buček]
|
190
|
+
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192
|
+
you may not use this file except in compliance with the License.
|
193
|
+
You may obtain a copy of the License at
|
194
|
+
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
196
|
+
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
|
+
See the License for the specific language governing permissions and
|
201
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# Logstash Javascript Filter
|
2
|
+
|
3
|
+
[![Travis Build Status](https://travis-ci.com/kares/logstash-filter-javascript.svg)](https://travis-ci.com/kares/logstash-filter-javascript)
|
4
|
+
|
5
|
+
This is a plugin for [Logstash](https://www.elastic.co/guide/en/logstash/current/introduction.html), compatible with LS
|
6
|
+
versions **>= 6.8**, that allows you to interact with pipeline events using scripts written in Javascript.
|
7
|
+
It works in a similar way as Logstash's (official) [Ruby filter](https://www.elastic.co/guide/en/logstash/current/plugins-filters-ruby.html).
|
8
|
+
|
9
|
+
**Only Java 8 - 13 is supported**, running LS on Java 15 or later won't work due the removal of the Nashorn Javascript engine.
|
10
|
+
|
11
|
+
**DISCLAIMER: Plugin is considered a (working) experiment and is no way as battle tested as (official) Logstash plugins supported
|
12
|
+
by [Elastic](https://www.elastic.co/support/matrix#matrix_logstash_plugins).**
|
13
|
+
|
14
|
+
In general, performance wise, you can expect the same throughput as with the Ruby filter.
|
15
|
+
Also, performance of the Nashorn Javascript engine *might* vary between Java versions.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
To inline Javascript in your filter, place all code in the `code` option. This code will be executed for every event the
|
20
|
+
filter receives. For example, to cancel 90% of events, you can do this:
|
21
|
+
```javascript
|
22
|
+
filter {
|
23
|
+
javascript {
|
24
|
+
code => "if (java.lang.Math.random() <= 0.9) event.cancel()"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
```
|
28
|
+
|
29
|
+
You can also place JS code in the `init` option - it will be executed only once during the plugin's initialization phase.
|
30
|
+
This is a great place to "feature validate" the Javascript engine:
|
31
|
+
|
32
|
+
```javascript
|
33
|
+
filter {
|
34
|
+
javascript {
|
35
|
+
init => "if (Number.MIN_VALUE <= 0) throw new Error(0); if (parseInt('f*ck', 16) !== 15) throw 'f*ck'"
|
36
|
+
code => 'event.setField("message", "b" + "a" + +"a" + "a")'
|
37
|
+
}
|
38
|
+
}
|
39
|
+
```
|
40
|
+
|
41
|
+
### Installing
|
42
|
+
|
43
|
+
`$LS_HOME/bin/logstash-plugin install logstash-filter-javascript`
|
44
|
+
|
45
|
+
### Configuration
|
46
|
+
|
47
|
+
TO-BE-CONTINUED...
|
48
|
+
|
49
|
+
### Differences from Ruby filter
|
50
|
+
|
51
|
+
Unlike the Ruby filter, which allows you to hook into the LS execution runtime, the Javascript filter starts an isolated
|
52
|
+
JS engine on every filter use.
|
53
|
+
|
54
|
+
There's no `new_event_block` callback hook implemented in the Javascript filter, this one (if requested) deserves more
|
55
|
+
thought as it just felt a bit "hacky" to copy what the Ruby filter does.
|
56
|
+
|
57
|
+
The Javascript filter does not expose a `register` function (with `script_params`), instead you can use `init_parameters`
|
58
|
+
to set variables in the global scope which will than be accessible from within the `filter` function.
|
59
|
+
|
60
|
+
### Tips & Tricks
|
61
|
+
|
62
|
+
Nashorn defaults to ECMAScript 5.1 by default which lacks the compact arrow `arg => ...` syntax or the `let` keyword.
|
63
|
+
There's (incomplete) support for ECMA 6 but requires setting a system properly, navigate to *config/jvm.options* and add :
|
64
|
+
```
|
65
|
+
-Dnashorn.args=--language=es6
|
66
|
+
```
|
67
|
+
|
68
|
+
Be aware of scripting Java types with Nashorn as not all native Javascript APIs will handle those seamlessly and
|
69
|
+
might lead to surprising results e.g.
|
70
|
+
|
71
|
+
```javascript
|
72
|
+
var json = JSON.stringify(event.toMap()); // undefined
|
73
|
+
// as JSON does not handle a java.util.Map returned from the LS event
|
74
|
+
|
75
|
+
// one can instead convert Java types to native JS objects e.g.
|
76
|
+
var map = event.toMap()
|
77
|
+
var obj = {}
|
78
|
+
for each (var key in map.keySet()) obj[key] = map.get(key) // Nashorn for-each extension for Java arrays/collections
|
79
|
+
```
|
80
|
+
|
81
|
+
You can set plain-old Javascript objects as values on the event, LS will see them as maps and convert them accordingly:
|
82
|
+
|
83
|
+
```javascript
|
84
|
+
var obj = { foo: "bar", truthy: true, aNull: null, number: 11.1 }
|
85
|
+
event.setField('js.values', obj)
|
86
|
+
// be aware when JS values contain function types as they might lead to issues
|
87
|
+
```
|
88
|
+
|
89
|
+
```javascript
|
90
|
+
// using JS types with a Java type system might lead to issues e.g. setting it on an event e.g.
|
91
|
+
event.setField('unexpected-value', undefined); // LS will complain not being able to handle :
|
92
|
+
// Missing Converter handling for full class name=jdk.nashorn.internal.runtime.Undefined
|
93
|
+
```
|
94
|
+
|
95
|
+
There's no `console.log` with Nashorn, however you could use Java's system output for debugging purposes :
|
96
|
+
```javascript
|
97
|
+
function puts(msg) {
|
98
|
+
java.lang.System.out.println(msg)
|
99
|
+
}
|
100
|
+
|
101
|
+
puts('event: ' + event.toMap());
|
102
|
+
|
103
|
+
// or simply the built-in print method :
|
104
|
+
print('event: ', event)
|
105
|
+
```
|
106
|
+
**NOTE**: be aware to remove such debugging statements in production to not fill up LS' standard output!
|
107
|
+
|
108
|
+
## Developing
|
109
|
+
|
110
|
+
### 1. Plugin Development and Testing
|
111
|
+
|
112
|
+
#### Code
|
113
|
+
|
114
|
+
- To get started, you'll need JRuby (>= 9.1) with the Bundler gem installed.
|
115
|
+
|
116
|
+
- Install dependencies
|
117
|
+
```sh
|
118
|
+
jruby -S bundle
|
119
|
+
```
|
120
|
+
|
121
|
+
#### Test
|
122
|
+
|
123
|
+
```sh
|
124
|
+
jruby -rbundler/setup -S rspec
|
125
|
+
```
|
126
|
+
|
127
|
+
### 2. Running your unpublished Plugin in Logstash
|
128
|
+
|
129
|
+
- Edit Logstash's `Gemfile` and add the local plugin path e.g.:
|
130
|
+
```ruby
|
131
|
+
gem "logstash-filter-javascript", :path => "path/to/local/logstash-filter-javascript"
|
132
|
+
```
|
133
|
+
- Install plugin
|
134
|
+
```sh
|
135
|
+
bin/logstash-plugin install --no-verify
|
136
|
+
```
|
137
|
+
- Run Logstash with your plugin
|
138
|
+
```sh
|
139
|
+
bin/logstash -e "filter { javascript { code => \"print('Hello from JS: ' + event.getField('message'))\" } }"
|
140
|
+
```
|
141
|
+
At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
|
142
|
+
|
143
|
+
#### 2.2 Run in an installed Logstash
|
144
|
+
|
145
|
+
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:
|
146
|
+
|
147
|
+
- Build your plugin gem
|
148
|
+
```sh
|
149
|
+
gem build logstash-filter-awesome.gemspec
|
150
|
+
```
|
151
|
+
- Install the plugin from the Logstash home
|
152
|
+
```sh
|
153
|
+
# Logstash 2.3 and higher
|
154
|
+
bin/logstash-plugin install --no-verify
|
155
|
+
|
156
|
+
# Prior to Logstash 2.3
|
157
|
+
bin/plugin install --no-verify
|
158
|
+
|
159
|
+
```
|
160
|
+
- Start Logstash and proceed to test the plugin
|
161
|
+
|
162
|
+
## Copyright
|
163
|
+
|
164
|
+
(c) 2020 [Karol Bucek](https://github.com/kares).
|
165
|
+
See LICENSE (http://www.apache.org/licenses/LICENSE-2.0) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/filters/base"
|
3
|
+
require "logstash/namespace"
|
4
|
+
|
5
|
+
# Execute JS code.
|
6
|
+
#
|
7
|
+
# For example, to cancel 90% of events, you can do this:
|
8
|
+
# [source,ruby]
|
9
|
+
# filter {
|
10
|
+
# javascript {
|
11
|
+
# # Cancel 90% of events
|
12
|
+
# code => "if (java.lang.Math.random() <= 0.9) event.cancel()"
|
13
|
+
# }
|
14
|
+
# }
|
15
|
+
module LogStash module Filters class Javascript < Base
|
16
|
+
|
17
|
+
java_import 'java.util.Map'
|
18
|
+
java_import 'jdk.nashorn.api.scripting.NashornException'
|
19
|
+
java_import 'jdk.nashorn.api.scripting.ScriptObjectMirror'
|
20
|
+
JEvent = org.logstash.Event
|
21
|
+
private_constant :JEvent
|
22
|
+
|
23
|
+
class ScriptError < StandardError; end
|
24
|
+
|
25
|
+
config_name "javascript"
|
26
|
+
|
27
|
+
# Any code to execute at plugin startup-time
|
28
|
+
config :init, :validate => :string
|
29
|
+
|
30
|
+
# Parameters for this specific script, these are set as globals.
|
31
|
+
config :init_parameters, :type => :hash, :default => {}
|
32
|
+
|
33
|
+
# The code to execute for every event.
|
34
|
+
# You will have an `event` variable available that is the event itself.
|
35
|
+
config :code, :validate => :string
|
36
|
+
|
37
|
+
# Path to the script.js
|
38
|
+
config :path, :validate => :path, :list => true
|
39
|
+
|
40
|
+
# Tag to add to events that cause an exception in the script filter
|
41
|
+
config :tag_on_exception, :type => :string, :default => "_javascriptexception"
|
42
|
+
|
43
|
+
# Flag for add exception message to tag_on_exception
|
44
|
+
#config :tag_with_exception_message, :type => :boolean, :default => false
|
45
|
+
|
46
|
+
def initialize(*params)
|
47
|
+
super(*params)
|
48
|
+
@script = Script.new(nil, init_parameters, logger)
|
49
|
+
@script.js_eval @init if @init
|
50
|
+
@js_filter = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def register
|
54
|
+
@js_filter = nil # TODO: devutils' sample helper causes plugin.register to happen twice
|
55
|
+
|
56
|
+
if @path
|
57
|
+
@path.each { |path| @script.js_eval(::File.read(path), path: path) }
|
58
|
+
@js_filter = @script.get('filter') # `expecting a `function filter(event) {}`
|
59
|
+
if @js_filter.nil?
|
60
|
+
raise ScriptError, "script at '#{@path}' does not define a filter(event) function" if @code.nil?
|
61
|
+
else
|
62
|
+
if @js_filter.is_a?(ScriptObjectMirror)
|
63
|
+
unless @js_filter.isFunction
|
64
|
+
raise ScriptError, "script at '#{@path}' defines a 'filter' property that isn't a function (got type: #{@js_filter.getClassName})"
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise ScriptError, "script at '#{@path}' defines a 'filter' property that isn't a function (got value: #{@js_filter.inspect})"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if @code
|
73
|
+
if @js_filter
|
74
|
+
msg = "Script(s) provided by 'path' defined a filter function while 'code' => ... is also set, this is ambiguous!"
|
75
|
+
@logger.error(msg); raise LogStash::ConfigurationError.new(msg)
|
76
|
+
end
|
77
|
+
|
78
|
+
@js_filter = @script.js_eval(@code) { "(function filter(event) {\n#{@code} } )" } # jdk.nashorn.api.scripting.JSObject
|
79
|
+
end
|
80
|
+
|
81
|
+
@script.verify
|
82
|
+
end
|
83
|
+
|
84
|
+
def filter(event, &block)
|
85
|
+
java_event = event.to_java
|
86
|
+
begin
|
87
|
+
js_return = @script.js_call(@js_filter, java_event)
|
88
|
+
filter_matched(event)
|
89
|
+
rescue => e
|
90
|
+
@logger.error("could not process event due:", error_details(e))
|
91
|
+
tag_exception(event, e)
|
92
|
+
return event
|
93
|
+
end
|
94
|
+
event.cancel unless filter_results(java_event, js_return, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# @param results (JS array) in a `jdk.nashorn.api.scripting.ScriptObjectMirror`
|
100
|
+
def filter_results(event, js_return)
|
101
|
+
if js_return.nil? # explicit `return null`
|
102
|
+
# drop event (return false)
|
103
|
+
elsif ScriptObjectMirror.isUndefined(js_return) # jdk.nashorn.internal.runtime.Undefined
|
104
|
+
return true # do not drop (assume it's been dealt with e.g. `event.cancel()`)
|
105
|
+
elsif js_return.is_a?(ScriptObjectMirror)
|
106
|
+
if js_return.isArray
|
107
|
+
i = 0; returned_original = false
|
108
|
+
while i < js_return.size
|
109
|
+
evt = js_return.getSlot(i)
|
110
|
+
if event.equal?(evt)
|
111
|
+
returned_original = true
|
112
|
+
else
|
113
|
+
yield wrap_event(evt)
|
114
|
+
end
|
115
|
+
i += 1
|
116
|
+
end
|
117
|
+
return returned_original
|
118
|
+
else
|
119
|
+
begin
|
120
|
+
evt = wrap_event(js_return) # JSObject implement Map interface
|
121
|
+
rescue => e
|
122
|
+
raise e # TODO we should attempt to provide a better exception here if we can not convert the JS (map) object
|
123
|
+
# raise ScriptError, "javascript did not return an event/array (or null) from 'filter', got: #{js_return.getClassName}"
|
124
|
+
else
|
125
|
+
yield evt
|
126
|
+
end
|
127
|
+
end
|
128
|
+
elsif js_return.is_a?(JEvent) || js_return.is_a?(Map)
|
129
|
+
return true if event.equal?(js_return)
|
130
|
+
yield wrap_event(js_return)
|
131
|
+
else
|
132
|
+
raise ScriptError, "javascript did not return an event/array (or null) from 'filter', got: #{js_return.inspect}"
|
133
|
+
end
|
134
|
+
false # script did not return original event
|
135
|
+
end
|
136
|
+
|
137
|
+
# NOTE: JS code is expected to work with Java event API
|
138
|
+
# @param js_event a org.logstash.Event or simply a plain-old (map-like) JS object
|
139
|
+
def wrap_event(js_event)
|
140
|
+
js_event = JEvent.new(js_event) unless js_event.is_a?(JEvent)
|
141
|
+
Event.new(js_event)
|
142
|
+
end
|
143
|
+
|
144
|
+
def tag_exception(event, e)
|
145
|
+
if @tag_with_exception_message && e.message
|
146
|
+
event.tag("#{@tag_on_exception}: #{e.message}")
|
147
|
+
end
|
148
|
+
event.tag(@tag_on_exception) if @tag_on_exception
|
149
|
+
end
|
150
|
+
|
151
|
+
def error_details(e)
|
152
|
+
details = { :exception => e.class, :message => e.message }
|
153
|
+
if e.is_a?(NashornException)
|
154
|
+
details[:message] = e.toString
|
155
|
+
details[:backtrace] = e.backtrace if logger.debug?
|
156
|
+
js_error = e.getEcmaError
|
157
|
+
js_error = js_error.toString if js_error.is_a?(ScriptObjectMirror)
|
158
|
+
details[:javascript_error] = js_error
|
159
|
+
details[:javascript_file] = e.getFileName # '<eval>' for incline code
|
160
|
+
details[:javascript_line] = e.getLineNumber # inline code lines are +1
|
161
|
+
details[:javascript_column] = e.getColumnNumber
|
162
|
+
details[:javascript_trace] = NashornException.getScriptFrames(e)
|
163
|
+
else
|
164
|
+
details[:backtrace] = e.backtrace
|
165
|
+
end
|
166
|
+
details
|
167
|
+
end
|
168
|
+
|
169
|
+
class Script
|
170
|
+
|
171
|
+
FILENAME = javax.script.ScriptEngine::FILENAME
|
172
|
+
|
173
|
+
attr_reader :context
|
174
|
+
|
175
|
+
# @param context the JS this context for the filter function
|
176
|
+
# @param params additional JS (key-value) parameters to set 'globally'
|
177
|
+
def initialize(context, params, logger)
|
178
|
+
@logger = logger
|
179
|
+
@engine = javax.script.ScriptEngineManager.new.getEngineByName("nashorn")
|
180
|
+
@context = context
|
181
|
+
|
182
|
+
factory = @engine.getFactory
|
183
|
+
logger.debug "initialized javascript (#{factory.getLanguageVersion}) engine:", name: factory.getEngineName, version: factory.getEngineVersion
|
184
|
+
|
185
|
+
params.each { |name, value| @engine.put(name, value) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def js_eval(code, path: nil)
|
189
|
+
filename = @engine.get(FILENAME)
|
190
|
+
@engine.put(FILENAME, path)
|
191
|
+
@engine.eval block_given? ? yield : code
|
192
|
+
rescue => e # (non-checked) Java::JavaxScript::ScriptException, e.g.
|
193
|
+
# Java::JavaxScript::ScriptException (TypeError: Cannot read property "far" from undefined in <eval> at line number 2)
|
194
|
+
@logger.error "failed to evaluate javascript code:", code_hint(code, path).merge(message: e.message)
|
195
|
+
raise e
|
196
|
+
ensure
|
197
|
+
@engine.put(FILENAME, filename)
|
198
|
+
end
|
199
|
+
|
200
|
+
def js_call(js_fn, arg)
|
201
|
+
js_fn.call(@context, arg)
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return nil if no such property
|
205
|
+
def get(name)
|
206
|
+
@engine.get(name)
|
207
|
+
end
|
208
|
+
|
209
|
+
def verify
|
210
|
+
true # NOTE can we do more JS code checks?
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
MAX_LINE_LENGTH = 50
|
216
|
+
|
217
|
+
def code_hint(code, path)
|
218
|
+
return { path: path } if path
|
219
|
+
lines = code.split("\n")
|
220
|
+
code = lines.find { |line| ! line.strip.empty? }
|
221
|
+
if code.length <= MAX_LINE_LENGTH
|
222
|
+
code = "#{code}..." if lines.size > 1
|
223
|
+
else
|
224
|
+
code = "#{code[0, MAX_LINE_LENGTH - 3]}..."
|
225
|
+
end
|
226
|
+
{ code: code }
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end end end
|