Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ddtrace/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
import simplejson as json
except ImportError:
import json

try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
4 changes: 3 additions & 1 deletion ddtrace/contrib/flask/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import logging

# project
from ...ext import http
from ...ext import http, errors

# 3p
from flask import g, request, signals
Expand Down Expand Up @@ -70,6 +70,8 @@ def _finish_span(self, response=None, exception=None):
if not response and exception:
error = 1
code = 500
span.set_tag(errors.ERROR_TYPE, type(exception))
span.set_tag(errors.ERROR_MSG, exception)

span.resource = str(request.endpoint or "").lower()
span.set_tag(http.URL, str(request.base_url or ""))
Expand Down
5 changes: 4 additions & 1 deletion ddtrace/contrib/flask/test_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ... import Tracer
from ...contrib.flask import TraceMiddleware
from ...test_tracer import DummyWriter
from ...ext import http
from ...ext import http, errors

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -227,4 +227,7 @@ def test_fatal(self):
assert s.start >= start
assert s.duration <= end - start
eq_(s.meta.get(http.STATUS_CODE), '500')
assert "ZeroDivisionError" in s.meta.get(errors.ERROR_TYPE)
msg = s.meta.get(errors.ERROR_MSG)
assert "integer division" in msg, msg

4 changes: 2 additions & 2 deletions ddtrace/contrib/pylons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def _start_response(status, *args, **kwargs):

try:
return self.app(environ, _start_response)
except Exception as e:
except Exception:
if span:
span.error = 1
span.set_traceback()
raise
finally:
if not span:
Expand Down
6 changes: 6 additions & 0 deletions ddtrace/contrib/sqlite3/test_sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ... import Tracer
from ...contrib.sqlite3 import connection_factory
from ...test_tracer import DummyWriter
from ...ext import errors

def test_foo():
writer = DummyWriter()
Expand Down Expand Up @@ -57,3 +58,8 @@ def test_foo():
eq_(span.meta["sql.query"], q)
eq_(span.error, 1)
eq_(span.span_type, "sql")
assert span.get_tag(errors.ERROR_STACK)
assert 'OperationalError' in span.get_tag(errors.ERROR_TYPE)
assert 'no such table' in span.get_tag(errors.ERROR_MSG)


17 changes: 17 additions & 0 deletions ddtrace/ext/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
tags for common error attributes
"""

import traceback


ERROR_MSG = "error.msg" # a string representing the error message
ERROR_TYPE = "error.type" # a string representing the type of the error
ERROR_STACK = "error.stack" # a human readable version of the stack. beta.

def get_traceback(tb=None, error=None):
t = None
if error:
t = type(error)
lines = traceback.format_exception(t, error, tb, limit=20)
return "\n".join(lines)
48 changes: 44 additions & 4 deletions ddtrace/span.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@

from compat import StringIO
import logging
import random
import sys
import time
import traceback

from .ext import errors


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -112,6 +117,39 @@ def set_tags(self, tags):
set_meta = set_tag
set_metas = set_tags

def set_traceback(self):
""" If the current stack has a traceback, tag the span with the
relevant error info.

>>> span.set_traceback()

is equivalent to:

>>> exc = sys.exc_info()
>>> span.set_exc_info(*exc)
"""
(exc_type, exc_val, exc_tb) = sys.exc_info()
self.set_exc_info(exc_type, exc_val, exc_tb)

def set_exc_info(self, exc_type, exc_val, exc_tb):
""" Tag the span with an error tuple as from `sys.exc_info()`. """
if not (exc_type and exc_val and exc_tb):
return # nothing to do

self.error = 1

# get the traceback
buff = StringIO()
traceback.print_exception(exc_type, exc_val, exc_tb, file=buff, limit=20)
tb = buff.getvalue()

# readable version of type (e.g. exceptions.ZeroDivisionError)
exc_type_str = "%s.%s" % (exc_type.__module__, exc_type.__name__)

self.set_tag(errors.ERROR_MSG, exc_val)
self.set_tag(errors.ERROR_TYPE, exc_type_str)
self.set_tag(errors.ERROR_STACK, tb)

def pprint(self):
""" Return a human readable version of the span. """
lines = [
Expand All @@ -133,10 +171,12 @@ def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
self.error = 1
# FIXME[matt] store traceback info
self.finish()
try:
if exc_type:
self.set_exc_info(exc_type, exc_val, exc_tb)
self.finish()
except Exception:
log.exception("error closing trace")

def __repr__(self):
return "<Span(id=%s,trace_id=%s,parent_id=%s,name=%s)>" % (
Expand Down
27 changes: 27 additions & 0 deletions ddtrace/test_span.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from nose.tools import eq_

from .span import Span
from .ext import errors


def test_ids():
Expand Down Expand Up @@ -56,6 +57,28 @@ def test_finish():
s2 = Span(tracer=None, name="foo")
s2.finish()

def test_traceback_with_error():
s = Span(None, "foo")
try:
1/0
except ZeroDivisionError:
s.set_traceback()
else:
assert 0, "should have failed"

assert s.error
assert 'by zero' in s.get_tag(errors.ERROR_MSG)
eq_("exceptions.ZeroDivisionError", s.get_tag(errors.ERROR_TYPE))
assert s.get_tag(errors.ERROR_STACK)

def test_traceback_without_error():
s = Span(None, "foo")
s.set_traceback()
assert not s.error
assert not s.get_tag(errors.ERROR_MSG)
assert not s.get_tag(errors.ERROR_TYPE)
assert not s.get_tag(errors.ERROR_STACK)

def test_ctx_mgr():
dt = DummyTracer()
s = Span(dt, "bar")
Expand All @@ -71,6 +94,10 @@ def test_ctx_mgr():
eq_(out, e)
assert s.duration > 0, s.duration
assert s.error
eq_(s.get_tag(errors.ERROR_MSG), "boo")
assert "Exception" in s.get_tag(errors.ERROR_TYPE)
assert s.get_tag(errors.ERROR_STACK)

else:
assert 0, "should have failed"

Expand Down