Skip to content

Commit 8d74bd5

Browse files
authored
Merge pull request #559 from kkedziak-splunk/feature/improve_logging
feat: Add exception logging in Script
2 parents 75806e6 + 3ef0519 commit 8d74bd5

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-4
lines changed

splunklib/modularinput/event_writer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# under the License.
1414

1515
import sys
16+
import traceback
1617

1718
from splunklib.utils import ensure_str
1819
from .event import ET
@@ -66,6 +67,25 @@ def log(self, severity, message):
6667
self._err.write(f"{severity} {message}\n")
6768
self._err.flush()
6869

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

splunklib/modularinput/script.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,12 @@ def run_script(self, args, event_writer, input_stream):
9191
event_writer.write_xml_document(root)
9292

9393
return 1
94-
err_string = "ERROR Invalid arguments to modular input script:" + ' '.join(
95-
args)
96-
event_writer._err.write(err_string)
94+
event_writer.log(EventWriter.ERROR, "Invalid arguments to modular input script:" + ' '.join(
95+
args))
9796
return 1
9897

9998
except Exception as e:
100-
event_writer.log(EventWriter.ERROR, str(e))
99+
event_writer.log_exception(str(e))
101100
return 1
102101

103102
@property

tests/modularinput/test_event.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
# under the License.
1616

1717

18+
import re
1819
import sys
20+
from io import StringIO
1921

2022
import pytest
2123

@@ -150,3 +152,29 @@ def test_write_xml_is_sane(capsys):
150152
found_xml = ET.fromstring(captured.out)
151153

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

tests/modularinput/test_script.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22

33
import io
4+
import re
45
import xml.etree.ElementTree as ET
56
from splunklib.client import Service
67
from splunklib.modularinput import Script, EventWriter, Scheme, Argument, Event
@@ -228,3 +229,37 @@ def stream_events(self, inputs, ew):
228229
assert output.err == ""
229230
assert isinstance(script.service, Service)
230231
assert script.service.authority == script.authority_uri
232+
233+
234+
def test_log_script_exception(monkeypatch):
235+
out, err = io.StringIO(), io.StringIO()
236+
237+
# Override abstract methods
238+
class NewScript(Script):
239+
def get_scheme(self):
240+
return None
241+
242+
def stream_events(self, inputs, ew):
243+
raise RuntimeError("Some error")
244+
245+
script = NewScript()
246+
input_configuration = data_open("data/conf_with_2_inputs.xml")
247+
248+
ew = EventWriter(out, err)
249+
250+
assert script.run_script([TEST_SCRIPT_PATH], ew, input_configuration) == 1
251+
252+
# Remove paths and line numbers
253+
err = re.sub(r'File "[^"]+', 'File "...', err.getvalue())
254+
err = re.sub(r'line \d+', 'line 123', err)
255+
256+
assert out.getvalue() == ""
257+
assert err == (
258+
'ERROR Some error - '
259+
'Traceback (most recent call last): '
260+
' File "...", line 123, in run_script '
261+
' self.stream_events(self._input_definition, event_writer) '
262+
' File "...", line 123, in stream_events '
263+
' raise RuntimeError("Some error") '
264+
'RuntimeError: Some error '
265+
)

0 commit comments

Comments
 (0)