Skip to content

Commit ca3d9eb

Browse files
Add exception logging in Script
1 parent c5ce1a6 commit ca3d9eb

File tree

4 files changed

+87
-4
lines changed

4 files changed

+87
-4
lines changed

splunklib/modularinput/event_writer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import absolute_import
1616
import sys
17+
import traceback
1718

1819
from splunklib.six import ensure_str
1920
from .event import ET
@@ -23,6 +24,7 @@
2324
except ImportError:
2425
from splunklib.six import StringIO
2526

27+
2628
class EventWriter(object):
2729
"""``EventWriter`` writes events and error messages to Splunk from a modular input.
2830
Its two important methods are ``writeEvent``, which takes an ``Event`` object,
@@ -71,6 +73,25 @@ def log(self, severity, message):
7173
self._err.write("%s %s\n" % (severity, message))
7274
self._err.flush()
7375

76+
def log_exception(self, message, exception=None, severity=None):
77+
"""Logs messages about the exception thrown by this modular input to Splunk.
78+
These messages will show up in Splunk's internal logs.
79+
80+
:param message: ``string``, message to log.
81+
:param exception: ``Exception``, exception thrown by this modular input; if none, sys.exc_info() is used
82+
:param severity: ``string``, severity of message, see severities defined as class constants. Default: ERROR
83+
"""
84+
if exception is not None:
85+
tb_str = traceback.format_exception(type(exception), exception, exception.__traceback__)
86+
else:
87+
tb_str = traceback.format_exc()
88+
89+
if severity is None:
90+
severity = EventWriter.ERROR
91+
92+
self._err.write(("%s %s - %s" % (severity, message, tb_str)).replace("\n", " "))
93+
self._err.flush()
94+
7495
def write_xml_document(self, document):
7596
"""Writes a string representation of an
7697
``ElementTree`` object to the output stream.

splunklib/modularinput/script.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,12 @@ def run_script(self, args, event_writer, input_stream):
9999

100100
return 1
101101
else:
102-
err_string = "ERROR Invalid arguments to modular input script:" + ' '.join(
103-
args)
104-
event_writer._err.write(err_string)
102+
event_writer.log(EventWriter.ERROR, "Invalid arguments to modular input script:" + ' '.join(
103+
args))
105104
return 1
106105

107106
except Exception as e:
108-
event_writer.log(EventWriter.ERROR, str(e))
107+
event_writer.log_exception(str(e))
109108
return 1
110109

111110
@property

tests/modularinput/test_event.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
from __future__ import absolute_import
1818

19+
import re
1920
import sys
21+
from io import StringIO
2022

2123
import pytest
2224

@@ -151,3 +153,29 @@ def test_write_xml_is_sane(capsys):
151153
found_xml = ET.fromstring(captured.out)
152154

153155
assert xml_compare(expected_xml, found_xml)
156+
157+
158+
def test_log_exception():
159+
out, err = StringIO(), StringIO()
160+
ew = EventWriter(out, err)
161+
162+
exc = Exception("Something happened!")
163+
164+
try:
165+
raise exc
166+
except:
167+
ew.log_exception("ex1")
168+
169+
assert out.getvalue() == ""
170+
171+
# Remove paths and line
172+
err = re.sub(r'File "[^"]+', 'File "...', err.getvalue())
173+
err = re.sub(r'line \d+', 'line 123', err)
174+
175+
# One line
176+
assert err == (
177+
'ERROR ex1 - Traceback (most recent call last): '
178+
' File "...", line 123, in test_log_exception '
179+
' raise exc '
180+
'Exception: Something happened! '
181+
)

tests/modularinput/test_script.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from splunklib.client import Service
44
from splunklib.modularinput import Script, EventWriter, Scheme, Argument, Event
55
import io
6+
import re
67

78
from splunklib.modularinput.utils import xml_compare
89
from tests.modularinput.modularinput_testlib import data_open
@@ -231,3 +232,37 @@ def stream_events(self, inputs, ew):
231232
assert output.err == ""
232233
assert isinstance(script.service, Service)
233234
assert script.service.authority == script.authority_uri
235+
236+
237+
def test_log_script_exception(monkeypatch):
238+
out, err = io.StringIO(), io.StringIO()
239+
240+
# Override abstract methods
241+
class NewScript(Script):
242+
def get_scheme(self):
243+
return None
244+
245+
def stream_events(self, inputs, ew):
246+
raise RuntimeError("Some error")
247+
248+
script = NewScript()
249+
input_configuration = data_open("data/conf_with_2_inputs.xml")
250+
251+
ew = EventWriter(out, err)
252+
253+
assert script.run_script([TEST_SCRIPT_PATH], ew, input_configuration) == 1
254+
255+
# Remove paths and line numbers
256+
err = re.sub(r'File "[^"]+', 'File "...', err.getvalue())
257+
err = re.sub(r'line \d+', 'line 123', err)
258+
259+
assert out.getvalue() == ""
260+
assert err == (
261+
'ERROR Some error - '
262+
'Traceback (most recent call last): '
263+
' File "...", line 123, in run_script '
264+
' self.stream_events(self._input_definition, event_writer) '
265+
' File "...", line 123, in stream_events '
266+
' raise RuntimeError("Some error") '
267+
'RuntimeError: Some error '
268+
)

0 commit comments

Comments
 (0)