Repository: census-instrumentation/opencensus-python (python)

Issue:
span: provide a helper to add annotations
I kindly would like to the addition of a helper method on Span, `add_annotation`

My use case is to add an annotation to the span like this
```python
with tracer.span(name='close') as span:
     if self.sock is None:
           span.add_annotation('Socket was already closed')
     else:
           span.add_annotation('Socket is still open')
           try:
                self.sock.close()
           except Exception as e:
                span.add_annotation('Encountered an exception while closing', recoverable=True, method='memcache-close'))
```

I ask because as a helper method makes it easy for annotations to be made, without having to import too much or go through all the docs.

Perhaps something that wraps `add_time_event` say:

```python
def add_annotation(self, description, **attributes):
    tevt = TimeEvent(datetime.utcnow(), Annotation(description, Attributes(attributes)))
    self.add_time_event(tevt)
```

Thank you.

Pull request:
trace: Span.add_annotation method
Fixes #164

Adds a convenience method so that users of the API
don't have to import a lot in order to add an annotation.
I found the need for this API while instrumenting a client.
It avoids userland imports of:
* datetime
* attributes.Attributes
* trace.time_event.Annotation
* trace.time_event.TimeEvent

abstracting the above work, to no imports and just:
```python
span.add_annotation('Here is my annotation', key1='Foo', key2=12)
```

I believe the other implementations have a
method that helps add annotations.

Language|MethodName|Resource
---|---|---
Go|Span.Annotate|https://godoc.org/go.opencensus.io/trace#Span.Annotate
Java|Span.addAnnotation|https://www.javadoc.io/doc/io.opencensus/opencensus-api/0.12.2

### Exhibit
![screen shot 2018-04-21 at 5 17 40 pm](https://user-images.githubusercontent.com/4898263/39090002-f1b25676-4587-11e8-92bb-619a407f99a7.png)

Patch:
diff --git a/opencensus/trace/span.py b/opencensus/trace/span.py
index 7995dfd..07bb8c2 100644
--- a/opencensus/trace/span.py
+++ b/opencensus/trace/span.py
@@ -176,6 +176,20 @@ class Span(object):
         """
         self.attributes[attribute_key] = attribute_value
 
+    def add_annotation(self, description, **attrs):
+        """Add an annotation to span.
+
+        :type description: str
+        :param description: A user-supplied message describing the event.
+                        The maximum length for the description is 256 bytes.
+
+        :type attrs: kwargs
+        :param attrs: keyworded arguments e.g. failed=True, name='Caching'
+        """
+        at = attributes.Attributes(attrs)
+        self.add_time_event(time_event_module.TimeEvent(datetime.utcnow(),
+                            time_event_module.Annotation(description, at)))
+
     def add_time_event(self, time_event):
         """Add a TimeEvent.
 


Test patch:
diff --git a/tests/unit/trace/test_span.py b/tests/unit/trace/test_span.py
index 78b0c5f..01f1473 100644
--- a/tests/unit/trace/test_span.py
+++ b/tests/unit/trace/test_span.py
@@ -152,6 +152,17 @@ class TestSpan(unittest.TestCase):
 
         self.assertEqual(len(span.time_events), 1)
 
+    def test_add_annotation(self):
+        span_name = 'test_span_name'
+        span = self._make_one(span_name)
+
+        span.add_annotation('This is a test', name='octo-span', age=98)
+
+        self.assertEqual(len(span.time_events), 1)
+        a0 = span.time_events[0].annotation
+        self.assertEqual(a0.description, 'This is a test')
+        self.assertEqual(a0.attributes.attributes, dict(name='octo-span', age=98))
+
     def test_add_link(self):
         from opencensus.trace.link import Link