QuickstartΒΆ
The following example shows most features of the Python SDK:
# -*- coding: utf-8 -*-
#
# Copyright 2018 Dynatrace LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''This example demonstrates instrumenting a (mocked) application that executes
a remote call that sometimes fails and does some database operations.'''
from __future__ import print_function # Python 2 compatibility.
import threading
import oneagent # SDK initialization functions
import oneagent.sdk as onesdk # All other SDK functions.
from oneagent.common import MessagingDestinationType
try: # Python 2 compatibility.
input = raw_input #pylint:disable=redefined-builtin
except NameError:
pass
IN_DEV_ENVIRONMENT = True # Let's assume we are *not* in production here...
getsdk = oneagent.get_sdk # Just to make the code shorter.
def traced_db_operation(dbinfo, sql):
print('+db', dbinfo, sql)
# Entering the with block automatically start the tracer.
with getsdk().trace_sql_database_request(dbinfo, sql) as tracer:
# In real-world code, you would do the actual database operation here,
# i.e. call the database's API.
# Set an optional "exit"-field on the tracer. Whenever there is a
# setter available on a tracer (as opposed to an optional parameter to a
# trace_* function), it may be called anytime between creating and
# ending the tracer (i.e. also after starting it).
tracer.set_round_trip_count(3)
print('-db', dbinfo, sql)
def outgoing_remote_call(success):
print('+remote')
# We use positional arguments to specify required values and named arguments
# to specify optional values.
call = getsdk().trace_outgoing_remote_call(
'dummyPyMethod', 'DummyPyService', 'dupypr://localhost/dummyEndpoint',
onesdk.Channel(onesdk.ChannelType.IN_PROCESS, 'localhost'),
protocol_name='DUMMY_PY_PROTOCOL')
try:
with call:
# Note that this property can only be accessed after starting the
# tracer. See the documentation on tagging for more information.
strtag = call.outgoing_dynatrace_string_tag
do_remote_call(strtag, success)
except RuntimeError: # Swallow the exception raised above.
pass
print('-remote')
failed = [None]
def do_remote_call_thread_func(strtag, success):
try:
print('+thread')
# We use positional arguments to specify required values and named
# arguments to specify optional values.
incall = getsdk().trace_incoming_remote_call(
'dummyPyMethod', 'DummyPyService',
'dupypr://localhost/dummyEndpoint',
protocol_name='DUMMY_PY_PROTOCOL', str_tag=strtag)
with incall:
if not success:
raise RuntimeError('Remote call failed on the server side.')
dbinfo = getsdk().create_database_info(
'Northwind', onesdk.DatabaseVendor.SQLSERVER,
onesdk.Channel(onesdk.ChannelType.TCP_IP, '10.0.0.42:6666'))
# This with-block will automatically free the database info handle
# at the end. Note that the handle is used for multiple tracers. In
# general, it is recommended to reuse database (and web application)
# info handles as often as possible (for efficiency reasons).
with dbinfo:
traced_db_operation(
dbinfo, "BEGIN TRAN;")
traced_db_operation(
dbinfo,
"SELECT TOP 1 qux FROM baz ORDER BY quux;")
traced_db_operation(
dbinfo,
"SELECT foo, bar FROM baz WHERE qux = 23")
traced_db_operation(
dbinfo,
"UPDATE baz SET foo = foo + 1 WHERE qux = 23;")
traced_db_operation(dbinfo, "COMMIT;")
print('-thread')
except Exception as e:
failed[0] = e
raise
def do_remote_call(strtag, success):
# This function simulates doing a remote call by calling a function
# do_remote_call_thread_func in another thread, passing a string tag. See
# the documentation on tagging for more information.
failed[0] = None
workerthread = threading.Thread(
target=do_remote_call_thread_func,
args=(strtag, success))
workerthread.start()
# Note that we need to join the thread, as all tagging assumes synchronous
# calls.
workerthread.join()
if failed[0] is not None:
raise failed[0] #pylint:disable=raising-bad-type
def mock_incoming_web_request():
sdk = getsdk()
wappinfo = sdk.create_web_application_info(
virtual_host='example.com', # Logical name of the host server.
application_id='MyWebApplication', # Unique web application ID.
context_root='/my-web-app/') # App's prefix of the path part of the URL.
with wappinfo:
# This with-block will automatically free web application info handle
# at the end. Note that the handle can be used for multiple tracers. In
# general, it is recommended to reuse web application info handles as
# often as possible (for efficiency reasons). For example, if you use
# WSGI, the web application info could be stored as an attribute of the
# application object.
#
# Note that different ways to specify headers, response headers and
# parameter (form fields) not shown here also exist. Consult the
# documentation for trace_incoming_web_request and
# IncomingWebRequestTracer.
wreq = sdk.trace_incoming_web_request(
wappinfo,
'http://example.com/my-web-app/foo?bar=baz',
'GET',
headers={'Host': 'example.com', 'X-foo': 'bar'},
remote_address='127.0.0.1:12345')
with wreq:
wreq.add_parameter('my_form_field', '1234')
# Process web request
wreq.add_response_headers({'Content-Length': '1234'})
wreq.set_status_code(200) # OK
# Add 3 different custom attributes.
sdk.add_custom_request_attribute('custom int attribute', 42)
sdk.add_custom_request_attribute('custom float attribute', 1.778)
sdk.add_custom_request_attribute('custom string attribute', 'snow is falling')
# This call will trigger the diagnostic callback.
sdk.add_custom_request_attribute('another key', None)
# This call simulates incoming messages.
mock_process_incoming_message()
def _process_my_outgoing_request(_tag):
pass
def mock_outgoing_web_request():
sdk = getsdk()
# Create tracer and and request headers.
tracer = sdk.trace_outgoing_web_request('http://example.com/their-web-app/bar?foo=foz', 'GET',
headers={'X-not-a-useful-header': 'python-was-here'})
with tracer:
# Now get the outgoing dynatrace tag. You have to add this tag as request header to your
# request if you want that the path is continued on the receiving site. Use the constant
# oneagent.common.DYNATRACE_HTTP_HEADER_NAME as request header name.
tag = tracer.outgoing_dynatrace_string_tag
# Here you process and send your web request.
_process_my_outgoing_request(tag)
# As soon as the response is received, you can add the response headers to the
# tracer and you shouldn't forget to set the status code, too.
tracer.add_response_headers({'Content-Length': '1234'})
tracer.set_status_code(200) # OK
def mock_process_incoming_message():
sdk = getsdk()
# Create the messaging system info object.
msi_handle = sdk.create_messaging_system_info(
'MyPythonSenderVendor', 'MyPythonDestination', MessagingDestinationType.QUEUE,
onesdk.Channel(onesdk.ChannelType.UNIX_DOMAIN_SOCKET, 'MyPythonChannelEndpoint'))
with msi_handle:
# Create the receive tracer for incoming messages.
with sdk.trace_incoming_message_receive(msi_handle):
print('here we wait for incoming messages ...')
# Create the tracer for processing incoming messages.
tracer = sdk.trace_incoming_message_process(msi_handle)
# Now we can set the vendor message and correlation IDs. It's possible to set them
# either before the tracer is started or afterwards. But they have to be set before
# the tracer ends.
tracer.set_vendor_message_id('message_id')
with tracer:
# Use tracecontext_get_current to log a trace/span ID identifiying the current node.
tinfo = sdk.tracecontext_get_current()
print('[!dt dt.trace_id={},dt.span_id={}] handle incoming message'.format(
tinfo.trace_id, tinfo.span_id))
tracer.set_correlation_id('correlation_id')
def mock_outgoing_message():
sdk = getsdk()
# Create the messaging system info object.
msi_handle = sdk.create_messaging_system_info(
'MyPythonReceiverVendor', 'MyPythonDestination', MessagingDestinationType.TOPIC,
onesdk.Channel(onesdk.ChannelType.TCP_IP, '10.11.12.13:1415'))
with msi_handle:
# Create the outgoing message tracer;
with sdk.trace_outgoing_message(msi_handle) as tracer:
# Set the message and correlation IDs.
tracer.set_vendor_message_id('msgId')
tracer.set_correlation_id('corrId')
print('handle outgoing message')
def mock_custom_service():
sdk = getsdk()
with sdk.trace_custom_service('my_fancy_transaction', 'MyFancyService'):
print('do some fancy stuff')
def _diag_callback(text):
print(text)
def main():
print('+main')
# This gathers arguments prefixed with '--dt_' from sys.argv into the
# returned list. See initialize below.
sdk_options = oneagent.sdkopts_from_commandline(remove=True)
# Before using the SDK you have to initialize the OneAgent. You can call oneagent.initialize()
# as often as you want, but you also have to call oneagent.shutdown() for every call to
# initialize() as well.
#
# Passing in the sdk_options is entirely optional and usually not required
# as all settings will be automatically provided by the Dynatrace OneAgent
# that is installed on the host.
init_result = oneagent.initialize(sdk_options)
try:
if init_result.error is not None:
print('Error during SDK initialization:', init_result.error)
# While not by much, it is a bit faster to cache the result of
# oneagent.get_sdk() instead of calling the function multiple times.
sdk = getsdk()
# Set the diagnostic callback. Strongly recommended.
sdk.set_diagnostic_callback(_diag_callback)
# Set the verbose callback.
# Not recommended in production as lots of messages can be emitted.
if IN_DEV_ENVIRONMENT:
sdk.set_verbose_callback(_diag_callback)
# The agent state is one of the integers in oneagent.sdk.AgentState.
print('Agent state:', sdk.agent_state)
# The instance attribute 'agent_found' indicates whether an agent could be found or not.
print('Agent found:', sdk.agent_found)
# If an agent was found but it is incompatible with this version of the SDK for Python
# then 'agent_is_compatible' would be set to false.
print('Agent is compatible:', sdk.agent_is_compatible)
# The agent version is a string holding both the OneAgent version and the
# OneAgent SDK for C/C++ version separated by a '/'.
print('Agent version:', sdk.agent_version_string)
mock_incoming_web_request()
mock_outgoing_web_request()
mock_outgoing_message()
mock_custom_service()
# We use trace_incoming_remote_call here, because it is one of the few
# calls that create a new path if none is running yet.
with sdk.trace_incoming_remote_call('main', 'main', 'main'):
# We want to start an asynchronous execution at this time, so we create an
# in-process link which we will use afterwards (or in a different thread).
link = sdk.create_in_process_link()
# Simulate some remote calls
outgoing_remote_call(success=True)
outgoing_remote_call(success=True)
outgoing_remote_call(success=False)
# Now the asynchronous execution starts. So we create an in-process tracer. We're using
# the in-process link which we've created above. This link specifies where the traced
# actions below will show up in the path.
with sdk.trace_in_process_link(link):
outgoing_remote_call(success=True)
print('-main')
input('Now wait until the path appears in the UI...')
finally:
shutdown_error = oneagent.shutdown()
if shutdown_error:
print('Error shutting down SDK:', shutdown_error)
if __name__ == '__main__':
main()
See the rest of the documentation and the README on GitHub for more information.