summaryrefslogtreecommitdiffstats
path: root/doc/20-script-debugger.md
blob: e8ee6db6c9620ca28004764ae66a50f141563fb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Script Debugger <a id="script-debugger"></a>

You can run the Icinga 2 daemon with the `-X` (`--script-debugger`)
parameter to enable the script debugger:

```bash
icinga2 daemon -X
```

When an exception occurs or the [debugger](17-language-reference.md#breakpoints)
keyword is encountered in a user script, Icinga 2 launches a console that
allows the user to debug the script.

You can also attach the script debugger to the [configuration validation](11-cli-commands.md#config-validation):

```bash
icinga2 daemon -C -X
```

Here is a list of common errors which can be diagnosed with the script debugger:

* Configuration errors e.g. [apply rules](03-monitoring-basics.md#using-apply)
* Errors in user-defined [functions](17-language-reference.md#functions)

## Debugging Configuration Errors <a id="script-debugger-config-errors"></a>

The following example illustrates the problem of a service [apply rule](03-monitoring-basics.md#using-apply-for)
which expects a dictionary value for `config`, but the host custom variable only
provides a string value:

```
object Host "script-debugger-host" {
  check_command = "icinga"

  vars.http_vhosts["example.org"] = "192.168.1.100" // a string value
}

apply Service for (http_vhost => config in host.vars.http_vhosts) {
  import "generic-service"

  vars += config // expects a dictionary

  check_command = "http"
}
```

The error message on config validation will warn about the wrong value type,
but does not provide any context which objects are affected.

Enable the script debugger and run the config validation:

```
# icinga2 daemon -C -X

Breakpoint encountered in /etc/icinga2/conf.d/services.conf: 59:67-65:1
Exception: Error: Error while evaluating expression: Cannot convert value of type 'String' to an object.
Location:
/etc/icinga2/conf.d/services.conf(62):   check_command = "http"
/etc/icinga2/conf.d/services.conf(63):
/etc/icinga2/conf.d/services.conf(64):   vars += config
                                         ^^^^^^^^^^^^^^
/etc/icinga2/conf.d/services.conf(65): }
/etc/icinga2/conf.d/services.conf(66):
You can inspect expressions (such as variables) by entering them at the prompt.
To leave the debugger and continue the program use "$continue".
<1> =>
```

You can print the variables `vars` and `config` to get an idea about
their values:

```
<1> => vars
null
<2> => config
"192.168.1.100"
<3> =>
```

The `vars` attribute has to be a dictionary. Trying to set this attribute to a string caused
the error in our configuration example.

In order to determine the name of the host where the value of the `config` variable came from
you can inspect attributes of the service object:

```
<3> => host_name
"script-debugger-host-01"
<4> => name
"http"
```

Additionally you can view the service object attributes by printing the value of `this`.

## Using Breakpoints <a id="script-debugger-breakpoints"></a>

In order to halt execution in a script you can use the `debugger` keyword:

```
object Host "script-debugger-host-02" {
  check_command = "dummy"
  check_interval = 5s

  vars.dummy_text = {{
    var text = "Hello from " + macro("$name$")
    debugger
    return text
  }}
}
```

Icinga 2 will spawn a debugger console every time the function is executed:

```
# icinga2 daemon -X
...
Breakpoint encountered in /etc/icinga2/tests/script-debugger.conf: 7:5-7:12
You can inspect expressions (such as variables) by entering them at the prompt.
To leave the debugger and continue the program use "$continue".
<1> => text
"Hello from script-debugger-host-02"
<2> => $continue
```

## Debugging API Filters <a id="script-debugger-api-filters"></a>

Queries against the [Icinga 2 REST API](12-icinga2-api.md#icinga2-api) can use
filters, just like available in `assign where` expressions. If these filters cause
an internal error, they return an empty result to the caller.

In order to analyse these server-side errors, you can use the script debugger.

The following example tries filter for all host objects where the custom variable
`os` is set. There are various possibilities to check that, one of them would be
`host.vars.os != ""`. Another idea is to use the [contains](18-library-reference.md#dictionary-contains) method on the custom
attribute dictionary like this: `host.vars.contains("os")`.

```bash
curl -k -s -u root:icinga -H 'Accept: application/json' -H 'X-HTTP-Method-Override: GET' \
 -X POST 'https://localhost:5665/v1/objects/services' \
 -d '{ "filter": "host.vars.contains(\"os\")", "attrs": [ "__name" ], "joins": [ "host.name", "host.vars" ], "pretty": true }'
```

This will fail on all hosts which don't have any custom variable specified.

```
# icinga2 daemon -X

Breakpoint encountered.
Exception: Error: Argument is not a callable object.
Location: in <API query>: 1:0-1:23
You can inspect expressions (such as variables) by entering them at the prompt.
To leave the debugger and continue the program use "$continue".

<1> => this.host

...

    	vars = null

<2> => $continue
```

By definition, a type method can only be invoked on an actual object.

In order to stay safe, add more checks to the API filter:

- `host.vars && host.vars.contains("os")` or
- `host.vars && typeof(host.vars) == Dictionary && host.vars.contains("os")`

Example:

```bash
curl -k -s -u root:icinga -H 'Accept: application/json' -H 'X-HTTP-Method-Override: GET' \
 -X POST 'https://localhost:5665/v1/objects/services' \
 -d '{ "filter": "host.vars && typeof(host.vars) == Dictionary && host.vars.contains(\"os\")", "attrs": [ "__name" ], "joins": [ "host.name", "host.vars" ], "pretty": true }'
```