107 lines
4.4 KiB
Plaintext
107 lines
4.4 KiB
Plaintext
|
[[modules-scripting-painless-debugging]]
|
||
|
=== Painless Debugging
|
||
|
|
||
|
experimental[The Painless scripting language is new and is still marked as experimental. The syntax or API may be changed in the future in non-backwards compatible ways if required.]
|
||
|
|
||
|
==== Debug.Explain
|
||
|
|
||
|
Painless doesn't have a
|
||
|
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop[REPL]
|
||
|
and while it'd be nice for it to have one one day, it wouldn't tell you the
|
||
|
whole story around debugging painless scripts embedded in Elasticsearch because
|
||
|
the data that the scripts have access to or "context" is so important. For now
|
||
|
the best way to debug embedded scripts is by throwing exceptions at choice
|
||
|
places. While you can throw your own exceptions
|
||
|
(`throw new Exception('whatever')`), Painless's sandbox prevents you from
|
||
|
accessing useful information like the type of an object. So Painless has a
|
||
|
utility method, `Debug.explain` which throws the exception for you. For
|
||
|
example, you can use the <<search-explain>> to explore the context available to
|
||
|
a <<query-dsl-script-query>>.
|
||
|
|
||
|
[source,js]
|
||
|
---------------------------------------------------------
|
||
|
PUT /hockey/player/1?refresh
|
||
|
{"first":"johnny","last":"gaudreau","goals":[9,27,1],"assists":[17,46,0],"gp":[26,82,1]}
|
||
|
|
||
|
POST /hockey/player/1/_explain
|
||
|
{
|
||
|
"query": {
|
||
|
"script": {
|
||
|
"script": "Debug.explain(doc.goals)"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
---------------------------------------------------------
|
||
|
// CONSOLE
|
||
|
// TEST[catch:/painless_explain_error/]
|
||
|
|
||
|
Which shows that the class of `doc.first` is
|
||
|
`org.elasticsearch.index.fielddata.ScriptDocValues$Longs` by responding with:
|
||
|
|
||
|
[source,js]
|
||
|
---------------------------------------------------------
|
||
|
{
|
||
|
"error": {
|
||
|
"type": "script_exception",
|
||
|
"class": "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
|
||
|
"to_string": "[1, 9, 27]",
|
||
|
...
|
||
|
},
|
||
|
"status": 500
|
||
|
}
|
||
|
---------------------------------------------------------
|
||
|
// TESTRESPONSE[s/\.\.\./"script_stack": $body.error.script_stack, "script": $body.error.script, "lang": $body.error.lang, "caused_by": $body.error.caused_by, "root_cause": $body.error.root_cause, "reason": $body.error.reason/]
|
||
|
|
||
|
You can use the same trick to see that `_source` is a `java.util.LinkedHashMap`
|
||
|
in the `_update` API:
|
||
|
|
||
|
[source,js]
|
||
|
---------------------------------------------------------
|
||
|
POST /hockey/player/1/_update
|
||
|
{
|
||
|
"script": "Debug.explain(ctx._source)"
|
||
|
}
|
||
|
---------------------------------------------------------
|
||
|
// CONSOLE
|
||
|
// TEST[continued catch:/painless_explain_error/]
|
||
|
|
||
|
The response looks like:
|
||
|
|
||
|
[source,js]
|
||
|
---------------------------------------------------------
|
||
|
{
|
||
|
"error" : {
|
||
|
"root_cause": ...,
|
||
|
"type": "illegal_argument_exception",
|
||
|
"reason": "failed to execute script",
|
||
|
"caused_by": {
|
||
|
"type": "script_exception",
|
||
|
"class": "java.util.LinkedHashMap",
|
||
|
"to_string": "{gp=[26, 82, 1], last=gaudreau, assists=[17, 46, 0], first=johnny, goals=[9, 27, 1]}",
|
||
|
...
|
||
|
}
|
||
|
},
|
||
|
"status": 400
|
||
|
}
|
||
|
---------------------------------------------------------
|
||
|
// TESTRESPONSE[s/"root_cause": \.\.\./"root_cause": $body.error.root_cause/]
|
||
|
// TESTRESPONSE[s/\.\.\./"script_stack": $body.error.caused_by.script_stack, "script": $body.error.caused_by.script, "lang": $body.error.caused_by.lang, "caused_by": $body.error.caused_by.caused_by, "reason": $body.error.caused_by.reason/]
|
||
|
// TESTRESPONSE[s/"to_string": ".+"/"to_string": $body.error.caused_by.to_string/]
|
||
|
|
||
|
// TODO we should build some javadoc like mashup so people don't have to jump through these hoops.
|
||
|
|
||
|
Once you have the class of an object you can go
|
||
|
https://github.com/elastic/elasticsearch/tree/{branch}/modules/lang-painless/src/main/resources/org/elasticsearch/painless[here]
|
||
|
and check the available methods. Painless uses a strict whitelist to prevent
|
||
|
scripts that don't work well with Elasticsearch and all whitelisted methods
|
||
|
are listed in a file named after the package of the object (everything before
|
||
|
the last `.`). So `java.util.Map` is listed in a file named `java.util.txt`
|
||
|
starting on the line that looks like `class Map -> java.util.Map {`.
|
||
|
|
||
|
With the list of whitelisted methods in hand you can turn to either
|
||
|
https://docs.oracle.com/javase/8/docs/api/[Javadoc],
|
||
|
https://github.com/elastic/elasticsearch/tree/{branch}[Elasticsearch's source tree]
|
||
|
or, for whitelisted methods ending in `*`, the
|
||
|
https://github.com/elastic/elasticsearch/blob/{branch}/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java[Augmentation]
|
||
|
class.
|