Skip to content

Commit fa4dd20

Browse files
authored
Merge pull request #21 from Dynatrace/release_1.4
2 parents f6278c7 + abf71f3 commit fa4dd20

20 files changed

+357
-607
lines changed

README.md

+45-6
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,16 @@ Dynatrace OneAgent version (it is the same as
5858

5959
|OneAgent SDK for Python|OneAgent SDK for C/C++|Dynatrace OneAgent|Support status |
6060
|:----------------------|:---------------------|:-----------------|:------------------|
61+
|1.4 |1.6.1 |≥1.179 |Supported |
6162
|1.3 |1.5.1 |≥1.179 |Supported |
62-
|1.2 |1.4.1 |≥1.161 |Supported |
63-
|1.1 |1.3.1 |≥1.151 |Supported |
63+
|1.2 |1.4.1 |≥1.161 |Deprecated¹ |
64+
|1.1 |1.3.1 |≥1.151 |Deprecated¹ |
6465
|1.0 |1.1.0 |≥1.141 |EAP (not supported)|
6566

67+
> 1. *Deprecated* releases of the OneAgent SDK for Python are still supported but this
68+
> might change with a future release. Applications using those deprecated versions should
69+
> be upgraded to the latest release.
70+
6671
<a name="#using-the-oneagent-sdk-for-python-in-your-application"></a>
6772
## Using the OneAgent SDK for Python in your application
6873

@@ -157,11 +162,15 @@ Unusual events that prevent an operation from completing successfully include:
157162
> **NOTE**: Use this as a development and debugging aid only. Your application should not rely on a calling sequence or any message content being set
158163
or passed to the callback.
159164

165+
During development, it is additionally recommended to use the "verbose callback" which also informs about other events that may be benign
166+
but can be very helpful in debugging, e.g. a PurePath that was not created because a Tracer is disabled by configuration, etc.
167+
160168
```python
161169
def _diag_callback(unicode_message):
162170
print(unicode_message)
163171

164172
sdk.set_diagnostic_callback(_diag_callback)
173+
sdk.set_verbose_callback(_diag_callback) # Do not use this callback in production
165174
```
166175

167176

@@ -569,7 +578,18 @@ For more information on forked child processes, take a look at those resources:
569578
To debug your OneAgent SDK for Python installation, execute the following Python code:
570579

571580
```python
581+
import logging
582+
import time
572583
import oneagent
584+
585+
log_handler = logging.StreamHandler()
586+
log_formatter = logging.Formatter(
587+
'%(asctime)s.%(msecs)03d UTC [%(thread)08x]'
588+
' %(levelname)-7s [%(name)-6s] %(message)s',
589+
'%Y-%m-%d %H:%M:%S')
590+
log_formatter.converter = time.gmtime
591+
log_handler.setFormatter(log_formatter)
592+
oneagent.logger.addHandler(log_handler)
573593
oneagent.logger.setLevel(1)
574594
init_result = oneagent.initialize(['loglevelsdk=finest', 'loglevel=finest'])
575595
print('InitResult=' + repr(init_result))
@@ -613,7 +633,6 @@ Known gotchas:
613633
directory, your platform is not supported. Otherwise, regardless if it works with that method or not, please report an issue as described
614634
in [Let us help you](#let-us-help-you).
615635

616-
617636
<a name="extended-sdk-state"></a>
618637
### Extended SDK State
619638

@@ -633,7 +652,12 @@ print('Agent is compatible:', oneagent.get_sdk().agent_is_compatible)
633652
# OneAgent SDK for C/C++ version separated by a '/'.
634653
print('Agent version:', oneagent.get_sdk().agent_version_string)
635654
```
655+
<a name="shutdown-crashes"></a>
656+
### Shutdown crashes
636657

658+
If your are experiencing crashes when your application exits, make
659+
sure the you uninitialized the SDK properly by calling its `shutdown`
660+
function.
637661

638662
<a name="repository-contents"></a>
639663
## Repository contents
@@ -695,13 +719,28 @@ SLAs don't apply for GitHub tickets.
695719

696720
SLAs apply according to the customer's support level.
697721

698-
699722
<a name="release-notes"></a>
700723
## Release notes
701724

702-
Please see the [GitHub releases page](https://github.com/Dynatrace/OneAgent-SDK-for-Python/releases),
703-
and the [PyPI release history](https://pypi.org/project/oneagent-sdk/#history).
725+
### Version 1.4.0
704726

727+
* Don't look for agent module in `PATH/LD_LIBRARY_PATH/..`. and
728+
disallow a relative path in the `DT_HOME` directory on Windows to prevent DLL hijacking issues.
729+
730+
* Fixed a bug that might lead to crashes in the SDK's shutdown phase
731+
732+
* Support for **Python versions < 3.5** is deprecated. The OneAgent SDK for Python
733+
will still work with this release, but this might change in the future.
734+
735+
* Following versions of the **OneAgent SDK for Python** are considered deprecated
736+
and might not be supported in the future. Applications using it should be upgraded to the latest release.
737+
* [Version 1.1.0](https://github.com/Dynatrace/OneAgent-SDK-for-Python/releases/tag/v1.1.0)
738+
* [Version 1.2.0](https://github.com/Dynatrace/OneAgent-SDK-for-Python/releases/tag/v1.2.0)
739+
* [Version 1.2.1](https://github.com/Dynatrace/OneAgent-SDK-for-Python/releases/tag/v1.2.1)
740+
741+
> For older versions of the OneAgent SDK for Python, please see
742+
the [GitHub releases page](https://github.com/Dynatrace/OneAgent-SDK-for-Python/releases),
743+
and the [PyPI release history](https://pypi.org/project/oneagent-sdk/#history).
705744

706745
<a name="license"></a>
707746
## License

constraints.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pylint<2.5

samples/basic-sdk-sample/basic_sdk_sample.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
except NameError:
3333
pass
3434

35+
IN_DEV_ENVIRONMENT = True # Let's assume we are *not* in production here...
36+
3537
getsdk = oneagent.get_sdk # Just to make the code shorter.
3638

3739
def traced_db_operation(dbinfo, sql):
@@ -269,9 +271,14 @@ def main():
269271
# oneagent.get_sdk() instead of calling the function multiple times.
270272
sdk = getsdk()
271273

272-
# Set the diagnostic callback.
274+
# Set the diagnostic callback. Strongly recommended.
273275
sdk.set_diagnostic_callback(_diag_callback)
274276

277+
# Set the verbose callback.
278+
# Not recommended in production as lots of messages can be emitted.
279+
if IN_DEV_ENVIRONMENT:
280+
sdk.set_verbose_callback(_diag_callback)
281+
275282
# The agent state is one of the integers in oneagent.sdk.AgentState.
276283
print('Agent state:', sdk.agent_state)
277284

samples/basic-sdk-sample/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
name='oneagent-sdk-basic-sample',
2929
version='0.0', # This sample is not separately versioned
3030

31-
install_requires=['oneagent-sdk==1.*,>=1.2'],
31+
install_requires=['oneagent-sdk==1.*,>=1.4'],
3232

3333
description='OneAgent SDK for Python: Basic sample application',
3434
long_description=long_description,

samples/fork-sdk-sample/fork_sdk_sample.py

+11
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,17 @@
3939
def do_some_fancy_stuff(proc_number):
4040
sdk = getsdk()
4141

42+
# Initially, the state in the child will be PRE_INITIALIZED (2).
43+
print('Agent fork state (child process before SDK call):', sdk.agent_fork_state)
44+
4245
# The agent state in the child process should be ACTIVE (0).
4346
print('Agent state (child process #{}): {}'.format(proc_number, sdk.agent_state), flush=True)
4447

48+
# After calling any SDK function but agent_fork_state,
49+
# the state in the child will be FULLY_INITIALIZED (3).
50+
# In this case the SDK function called was the agent_state property accessed above.
51+
print('Agent fork state (child process after SDK call):', sdk.agent_fork_state)
52+
4553
print('Agent found:', sdk.agent_found)
4654
print('Agent is compatible:', sdk.agent_is_compatible)
4755
print('Agent version:', sdk.agent_version_string)
@@ -105,6 +113,9 @@ def main():
105113
# Since we're using the 'forkable' mode the state will be TEMPORARILY_INACTIVE (1) on Linux.
106114
print('Agent state (parent process):', sdk.agent_state)
107115

116+
# In the parent, the state will be PARENT_INITIALIZED (1).
117+
print('Agent fork state (parent process):', sdk.agent_fork_state)
118+
108119
# The instance attribute 'agent_found' indicates whether an agent could be found or not.
109120
print('Agent found:', sdk.agent_found)
110121

samples/fork-sdk-sample/setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
name='oneagent-sdk-fork-sample',
2929
version='0.0', # This sample is not separately versioned
3030

31-
install_requires=['oneagent-sdk==1.*,>=1.3'],
31+
install_requires=['oneagent-sdk==1.*,>=1.4'],
3232

3333
description='OneAgent SDK for Python: Fork sample application',
3434
long_description=long_description,
@@ -38,7 +38,7 @@
3838
maintainer_email='[email protected]',
3939
license='Apache License 2.0',
4040
entry_points={
41-
'console_scripts': ['oneagent-sdk-basic-sample=fork_sdk_sample:main'],
41+
'console_scripts': ['oneagent-sdk-fork-sample=fork_sdk_sample:main'],
4242
},
4343
classifiers=[
4444
'Intended Audience :: Developers',

src/oneagent/__init__.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,35 @@
8484
from oneagent._impl.six.moves import range #pylint:disable=import-error
8585
from oneagent.version import __version__
8686

87-
from .common import SDKError, SDKInitializationError, ErrorCode, _ONESDK_INIT_FLAG_FORKABLE
87+
from .common import (
88+
SDKError, SDKInitializationError, ErrorCode,
89+
_ONESDK_INIT_FLAG_FORKABLE, _add_enum_helpers)
8890
from ._impl.native import nativeagent
8991
from ._impl.native.nativeagent import try_get_sdk
9092
from ._impl.native.sdknulliface import SDKNullInterface
9193
from ._impl.native.sdkdllinfo import WIN32
9294

95+
if hasattr(sys, 'implementation'):
96+
def _get_py_edition():
97+
return sys.implementation.name # pylint:disable=no-member
98+
else:
99+
import platform
100+
101+
def _get_py_edition():
102+
return platform.python_implementation()
103+
93104
logger = logging.getLogger('py_sdk')
94105
logger.setLevel(logging.CRITICAL + 1) # Disabled by default
95106

107+
_PROCESS_TECH_PYTHON = 28
108+
_PROCESS_TECH_ONEAGENT_SDK = 118
109+
110+
def _get_py_version():
111+
return '.'.join(map(str, sys.version_info[:3])) + (
112+
'' if sys.version_info.releaselevel == "final"
113+
else sys.version_info.releaselevel + str(sys.version_info.serial))
114+
115+
@_add_enum_helpers
96116
class InitResult(namedtuple('InitResult', 'status error')):
97117
__slots__ = ()
98118

@@ -104,6 +124,10 @@ class InitResult(namedtuple('InitResult', 'status error')):
104124

105125
__nonzero__ = __bool__ = lambda self: self.status >= 0
106126

127+
def __repr__(self):
128+
return "InitResult(status={}, error={!r})".format(
129+
self._value_name(self.status), self.error) #pylint:disable=no-member
130+
107131

108132
_sdk_ref_lk = Lock()
109133
_sdk_ref_count = 0
@@ -186,8 +210,9 @@ def initialize(sdkopts=(), sdklibname=None, forkable=False):
186210
* :meth:`oneagent.sdk.SDK.agent_version_string` works as expected.
187211
* :meth:`oneagent.sdk.SDK.agent_state` will return
188212
:data:`oneagent.common.AgentState.TEMPORARILY_INACTIVE` - but see the note below.
189-
* :meth:`oneagent.sdk.SDK.set_diagnostic_callback` works as expected, the callback will be
190-
carried over to forked child processes.
213+
* :meth:`oneagent.sdk.SDK.set_diagnostic_callback` and
214+
:meth:`oneagent.sdk.SDK.set_verbose_callback` work as expected,
215+
the callback will be carried over to forked child processes.
191216
* It is recommended you call :func:`shutdown` when the original process will not fork any more
192217
children that want to use the SDK.
193218
@@ -248,8 +273,8 @@ def _try_init_noref(sdkopts=(), sdklibname=None, forkable=False):
248273

249274
try:
250275
logger.info(
251-
'Initializing SDK with options=%s, libname=%s.',
252-
sdkopts, sdklibname)
276+
'Initializing SDK on Python=%s with options=%s, libname=%s.',
277+
(sys.version or '?').replace('\n', ' ').replace('\r', ''), sdkopts, sdklibname)
253278
sdk = nativeagent.initialize(sdklibname)
254279

255280
have_warning = False
@@ -270,7 +295,11 @@ def _try_init_noref(sdkopts=(), sdklibname=None, forkable=False):
270295

271296
nativeagent.checkresult(sdk, sdk.initialize(flags), 'onesdk_initialize_2')
272297
_should_shutdown = True
273-
logger.debug("initialize successful")
298+
logger.debug('initialize successful, adding tech types...')
299+
sdk.ex_agent_add_process_technology(_PROCESS_TECH_ONEAGENT_SDK, 'Python', __version__)
300+
sdk.ex_agent_add_process_technology(
301+
_PROCESS_TECH_PYTHON, _get_py_edition(), _get_py_version())
302+
logger.debug('tech type reporting complete')
274303
return InitResult(
275304
(InitResult.STATUS_INITIALIZED_WITH_WARNING if have_warning else
276305
InitResult.STATUS_INITIALIZED),
@@ -280,7 +309,7 @@ def _try_init_noref(sdkopts=(), sdklibname=None, forkable=False):
280309
#pylint:disable=no-member
281310
if isinstance(e, SDKError) and e.code == ErrorCode.AGENT_NOT_ACTIVE:
282311
#pylint:enable=no-member
283-
logger.debug("initialized, but agent not active")
312+
logger.debug('initialized, but agent not active')
284313
return InitResult(InitResult.STATUS_INITIALIZED_WITH_WARNING, e)
285314
logger.exception('Failed initializing agent.')
286315
sdk = nativeagent.try_get_sdk()

src/oneagent/_impl/native/sdkctypesiface.py

+52-12
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,16 @@ def __init__(self, libname):
275275
(),
276276
result_t)
277277

278-
# Missing: agent_internl_dispatch(int32, void*); probably useless
278+
initfn(
279+
'agent_set_warning_callback',
280+
(agent_logging_callback_t,),
281+
result_t,
282+
public=False)
279283

280284
initfn(
281-
'agent_set_logging_callback',
285+
'agent_set_verbose_callback',
282286
(agent_logging_callback_t,),
283-
None,
287+
result_t,
284288
public=False)
285289

286290
initfn(
@@ -289,6 +293,22 @@ def __init__(self, libname):
289293
xchar_p,
290294
public=False)
291295

296+
297+
initfn(
298+
'agent_get_fork_state',
299+
(),
300+
ctypes.c_int32)
301+
302+
initfn(
303+
'ex_api_enable_techtype',
304+
(),
305+
result_t,
306+
public=False)
307+
initfn(
308+
'ex_agent_add_process_technology_p',
309+
(ctypes.c_int32, CCStringPInArg, CCStringPInArg),
310+
None).__doc__ = '''(tech_type, tech_edition, tech_version)'''
311+
292312
# Specific nodes
293313

294314
## Database
@@ -559,6 +579,12 @@ def initialize(self, init_flags=0):
559579
self._agent_found = found.value != 0
560580
self._agent_is_compatible = compatible.value != 0
561581

582+
enable_result = self._ex_api_enable_techtype()
583+
if self._agent_is_compatible and enable_result != ErrorCode.SUCCESS:
584+
logger.warning(
585+
"Tech type reporting API could not be enabled: %d %s",
586+
enable_result, self.strerror(enable_result))
587+
562588
return result
563589

564590
def agent_found(self):
@@ -678,24 +704,38 @@ def cb_wrapper(level, msg):
678704
def agent_get_version_string(self):
679705
return self._agent_version
680706

681-
def agent_set_logging_callback(self, callback):
707+
def _invoke_agent_log_cb_setter(self, callback, setter, update_stored_cb):
708+
result = None
709+
store_c_cb = lambda c_cb: setattr(self, "_diag_cb_" + setter.__name__, c_cb)
682710
if callback is None:
683-
self._agent_set_logging_callback(
684-
ctypes.cast(None, agent_logging_callback_t))
685-
self._diag_cb = None
686-
self._py_diag_cb = None
711+
result = setter(ctypes.cast(None, agent_logging_callback_t))
712+
if result == ErrorCode.SUCCESS:
713+
store_c_cb(None)
714+
if update_stored_cb:
715+
self._py_diag_cb = None
687716
else:
688-
689717
@wraps(callback)
690718
def cb_wrapper(msg):
691719
if isinstance(msg, six.binary_type):
692720
msg = u8_to_str(msg)
693721
return callback(msg)
694722

695723
c_cb = agent_logging_callback_t(cb_wrapper)
696-
self._agent_set_logging_callback(c_cb)
697-
self._diag_cb = c_cb
698-
self._py_diag_cb = cb_wrapper
724+
result = setter(c_cb)
725+
if result == ErrorCode.SUCCESS:
726+
store_c_cb(c_cb)
727+
if update_stored_cb:
728+
self._py_diag_cb = cb_wrapper
729+
return result
730+
731+
732+
def agent_set_warning_callback(self, callback):
733+
return self._invoke_agent_log_cb_setter(
734+
callback, self._agent_set_warning_callback, update_stored_cb=True)
735+
736+
def agent_set_verbose_callback(self, callback):
737+
return self._invoke_agent_log_cb_setter(
738+
callback, self._agent_set_verbose_callback, update_stored_cb=False)
699739

700740
def __del__(self):
701741
# __del__ is also called when __init__ fails, so safeguard against that

0 commit comments

Comments
 (0)