diff --git a/README.md b/README.md
index b5954dd0..728f654a 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,31 @@ If you use uWSGI in forking workers mode, you must specify `--lazy-apps` (or `la
The instana package will automatically collect key metrics from your Python processes. Just install and go.
+## Want End User Monitoring?
+
+Instana provides deep end user monitoring that links server side traces with browser events to give you a complete view from server to browser.
+
+For Python templates and views, get your EUM API key from your Instana dashboard and you can call `instana.helpers.eum_snippet(api_key='abc')` from within your layout file. This will output
+a small javascript snippet of code to instrument browser events. It's based on [Weasel](https://github.com/instana/weasel). Check it out.
+
+As an example, you could do the following:
+
+```python
+from instana.helpers import eum_snippet
+
+instana.api_key = 'abc'
+meta_kvs = { 'username': user.name }
+
+# This will return a string containing the EUM javascript for the layout or view.
+eum_snippet(meta=meta_kvs)
+```
+
+The optional second argument to `eum_snippet()` is a hash of metadata key/values that will be reported along with the browser instrumentation.
+
+
+
+See also the [End User Monitoring](https://docs.instana.io/products/website_monitoring/#configuration) in the Instana documentation portal.
+
## OpenTracing
This Python package supports [OpenTracing](http://opentracing.io/). When using this package, the OpenTracing tracer (`opentracing.tracer`) is automatically set to the `InstanaTracer`.
diff --git a/instana/__init__.py b/instana/__init__.py
index e76cb6ac..6af19303 100644
--- a/instana/__init__.py
+++ b/instana/__init__.py
@@ -58,5 +58,8 @@
# instana.service_name = "myservice"
service_name = None
+# User configurable EUM API key for instana.helpers.eum_snippet()
+eum_api_key = ''
+
if "INSTANA_SERVICE_NAME" in os.environ:
service_name = os.environ["INSTANA_SERVICE_NAME"]
diff --git a/instana/eum.js b/instana/eum.js
new file mode 100644
index 00000000..c5390faf
--- /dev/null
+++ b/instana/eum.js
@@ -0,0 +1,10 @@
+
diff --git a/instana/eum_test.js b/instana/eum_test.js
new file mode 100644
index 00000000..e1fff8bc
--- /dev/null
+++ b/instana/eum_test.js
@@ -0,0 +1,12 @@
+
diff --git a/instana/helpers.py b/instana/helpers.py
new file mode 100644
index 00000000..0fe08680
--- /dev/null
+++ b/instana/helpers.py
@@ -0,0 +1,100 @@
+import os
+from string import Template
+from instana import internal_tracer, eum_api_key as global_eum_api_key
+from instana.log import logger
+
+# Usage:
+#
+# from instana.helpers import eum_snippet
+# meta_kvs = { 'userId': user.id }
+# eum_snippet(meta=meta_kvs)
+
+
+def eum_snippet(trace_id=None, eum_api_key=None, meta={}):
+ """
+ Return an EUM snippet for use in views, templates and layouts that reports
+ client side metrics to Instana that will automagically be linked to the
+ current trace.
+
+ @param trace_id [optional] the trace ID to insert into the EUM string
+ @param eum_api_key [optional] the EUM API key from your Instana dashboard
+ @param meta [optional] optional additional KVs you want reported with the
+ EUM metrics
+
+ @return string
+ """
+ try:
+ eum_file = open(os.path.dirname(__file__) + '/eum.js')
+ eum_src = Template(eum_file.read())
+
+ # Prepare the standard required IDs
+ ids = {}
+ ids['meta_kvs'] = ''
+
+ current_ctx = internal_tracer.current_context()
+
+ if trace_id or current_ctx:
+ ids['trace_id'] = trace_id or current_ctx.trace_id
+ else:
+ # No trace_id passed in and tracer doesn't show an active span so
+ # return nothing, nada & zip.
+ return ''
+
+ if eum_api_key:
+ ids['eum_api_key'] = eum_api_key
+ else:
+ ids['eum_api_key'] = global_eum_api_key
+
+ # Process passed in EUM 'meta' key/values
+ for key, value in meta.items():
+ ids['meta_kvs'] += ("'ineum('meta', '%s', '%s');'" % (key, value))
+
+ return eum_src.substitute(ids)
+ except Exception as e:
+ logger.debug(e)
+ return ''
+
+def eum_test_snippet(trace_id=None, eum_api_key=None, meta={}):
+ """
+ Return an EUM snippet for use in views, templates and layouts that reports
+ client side metrics to Instana that will automagically be linked to the
+ current trace.
+
+ @param trace_id [optional] the trace ID to insert into the EUM string
+ @param eum_api_key [optional] the EUM API key from your Instana dashboard
+ @param meta [optional] optional additional KVs you want reported with the
+ EUM metrics
+
+ @return string
+ """
+
+ try:
+ eum_file = open(os.path.dirname(__file__) + '/eum_test.js')
+ eum_src = Template(eum_file.read())
+
+ # Prepare the standard required IDs
+ ids = {}
+ ids['meta_kvs'] = ''
+
+ current_ctx = internal_tracer.current_context()
+
+ if trace_id or current_ctx:
+ ids['trace_id'] = trace_id or current_ctx.trace_id
+ else:
+ # No trace_id passed in and tracer doesn't show an active span so
+ # return nothing, nada & zip.
+ return ''
+
+ if eum_api_key:
+ ids['eum_api_key'] = eum_api_key
+ else:
+ ids['eum_api_key'] = global_eum_api_key
+
+ # Process passed in EUM 'meta' key/values
+ for key, value in meta.items():
+ ids['meta_kvs'] += ("'ineum('meta', '%s', '%s');'" % (key, value))
+
+ return eum_src.substitute(ids)
+ except Exception as e:
+ logger.debug(e)
+ return ''
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
new file mode 100644
index 00000000..cfb6cc01
--- /dev/null
+++ b/tests/test_helpers.py
@@ -0,0 +1,81 @@
+from nose.tools import assert_equals
+from instana.helpers import eum_snippet, eum_test_snippet
+
+# fake trace_id to test against
+trace_id = "aMLx9G2GnnQ6QyMCLJLuCM8nw"
+# fake api key to test against
+eum_api_key = "FJB66VjwGgGQX6jiCpekoR4vf"
+
+# fake meta key/values
+meta1 = "Z7RmMKQAiyCLEAmseNy7e6Vm4"
+meta2 = "Dp2bowfm6kJVD9CccmyBt4ePD"
+meta3 = "N4poUwbNz98YcvWRAizy2phCo"
+
+
+def test_vanilla_eum_snippet():
+ eum_string = eum_snippet(trace_id=trace_id, eum_api_key=eum_api_key)
+ assert type(eum_string) is str
+
+ assert eum_string.find(trace_id) != -1
+ assert eum_string.find(eum_api_key) != -1
+
+def test_eum_snippet_with_meta():
+ meta_kvs = {}
+ meta_kvs['meta1'] = meta1
+ meta_kvs['meta2'] = meta2
+ meta_kvs['meta3'] = meta3
+
+ eum_string = eum_snippet(trace_id=trace_id, eum_api_key=eum_api_key, meta=meta_kvs)
+ assert type(eum_string) is str
+
+ assert eum_string.find(trace_id) != -1
+ assert eum_string.find(eum_api_key) != -1
+ assert eum_string.find(meta1) != -1
+ assert eum_string.find(meta2) != -1
+ assert eum_string.find(meta3) != -1
+
+def test_eum_snippet_error():
+ meta_kvs = {}
+ meta_kvs['meta1'] = meta1
+ meta_kvs['meta2'] = meta2
+ meta_kvs['meta3'] = meta3
+
+ # No active span on tracer & no trace_id passed in.
+ eum_string = eum_snippet(eum_api_key=eum_api_key, meta=meta_kvs)
+ assert_equals('', eum_string)
+
+def test_vanilla_eum_test_snippet():
+ eum_string = eum_test_snippet(trace_id=trace_id, eum_api_key=eum_api_key)
+ assert type(eum_string) is str
+
+ assert eum_string.find(trace_id) != -1
+ assert eum_string.find(eum_api_key) != -1
+ assert eum_string.find('reportingUrl') != -1
+ assert eum_string.find('//eum-test-fullstack-0-us-west-2.instana.io') != -1
+
+def test_eum_test_snippet_with_meta():
+ meta_kvs = {}
+ meta_kvs['meta1'] = meta1
+ meta_kvs['meta2'] = meta2
+ meta_kvs['meta3'] = meta3
+
+ eum_string = eum_test_snippet(trace_id=trace_id, eum_api_key=eum_api_key, meta=meta_kvs)
+ assert type(eum_string) is str
+ assert eum_string.find('reportingUrl') != -1
+ assert eum_string.find('//eum-test-fullstack-0-us-west-2.instana.io') != -1
+
+ assert eum_string.find(trace_id) != -1
+ assert eum_string.find(eum_api_key) != -1
+ assert eum_string.find(meta1) != -1
+ assert eum_string.find(meta2) != -1
+ assert eum_string.find(meta3) != -1
+
+def test_eum_test_snippet_error():
+ meta_kvs = {}
+ meta_kvs['meta1'] = meta1
+ meta_kvs['meta2'] = meta2
+ meta_kvs['meta3'] = meta3
+
+ # No active span on tracer & no trace_id passed in.
+ eum_string = eum_test_snippet(eum_api_key=eum_api_key, meta=meta_kvs)
+ assert_equals('', eum_string)