/OneAgent-SDK-for-C

Enables custom tracing of native applications in Dynatrace

Primary LanguageCApache License 2.0Apache-2.0

Dynatrace OneAgent SDK for C/C++

This SDK enables Dynatrace customers to extend request level visibility into any native process. The SDK is C based and thus can be used in any C or C++ application. It can also be used in other languages via language bindings. In order to use the development kit you need to have access to the source code of the application in question.

This is the official C/C++ implementation of the Dynatrace OneAgent SDK.

Package contents

The SDK package includes

  • lib and include: The libraries and header files necessary for instrumenting applications.
  • *.cmake: Optional support files to use the libraries more easily with the CMake build system.
  • samples/sample1: A simple sample application.
  • docs: Reference documentation.

Features

When tracing incoming or outgoing calls, requests or messages, this SDK is compatible with other OneAgent SDKs and OneAgent code modules in general.

See Planned features for OneAgent SDK for details on upcoming features.

Documentation

The reference documentation is included in this package. The most recent version is also available online at https://dynatrace.github.io/OneAgent-SDK-for-C/.

A high level documentation/description of OneAgent SDK concepts is available at https://github.com/Dynatrace/OneAgent-SDK/.

Getting started

Getting the SDK

To start using the Dynatrace OneAgent SDK for C/C++, simply download the latest source archive from releases. The source archive also includes all necessary artifacts (e.g. the static and dynamic library files), so this is all you need. Extract the archive to a local folder on your machine and then add the appropriate "include" and "lib" paths to your build system.

To see if your platform is supported, refer to requirements.

Building and linking against the Dynatrace OneAgent SDK

The SDK doesn't have to be compiled, you only need to link your application to the SDK libraries.

Using CMake

If you use CMake to generate build files for your application, you should be able to use the provided onesdk-config.cmake script ala

include("path/to/sdk-package/onesdk-config.cmake")
target_link_libraries(your_application onesdk_static)

Auto-linking with Visual Studio

If you use Visual Studio to build a Windows application, you can use the SDK's auto-linking feature. To do this, simply define the preprocessor macro ONESDK_AUTO_LINK before including any SDK header file. Aside from that, you only have to add the appropriate "include" and "lib" paths.

Other build systems

If you use another build system you have to configure it to

  • add an "include" path to path/to/sdk-package/include
  • add a "lib" path to the appropriate platform subdirectory under lib (e.g. path/to/sdk-package/lib/linux-x86_64)
  • link the appropriate library (e.g. libonesdk_static.a)

The SDK contains code that dynamically loads the agent library (.dll/.so/...), so depending on your platform you might need to link additional libraries (e.g. under Linux you would typically add -ldl to the linker command line).

On Windows, when using Visual Studio 2015 or later, you also have to link legacy_stdio_definitions.lib.

Using CMake to build the samples

Assuming that you have a C++11 compiler and suitable build system installed (e.g. Visual Studio or g++ & make), which are supported and correctly detected by CMake, creating build files for the samples can be as easy as

C:\onesdk\samples>mkdir build
C:\onesdk\samples>cd build
C:\onesdk\samples\build>cmake ..
  *snip* a lot of CMake output
-- Build files have been written to: C:/onesdk/samples/build
C:\onesdk\samples\build>

Then simply use your build system to build the samples (e.g. "make" or open & build the generated solution in Visual Studio).

Initializing the Dynatrace OneAgent SDK

To initialize the OneAgent SDK, you call onesdk_initialize, like in the following sample. It is higly recommended that you call onesdk_shutdown when the application is done using the SDK (typically just before exiting):

#include <onesdk/onesdk.h>
#include <stdio.h>

static void mycallback(char const* message) {
    fputs(message, stderr);
}

int main(int argc, char** argv) {
    onesdk_stub_process_cmdline_args(argc, argv, 1);  /* optional: let the SDK process command line arguments   */
    onesdk_stub_strip_sdk_cmdline_args(&argc, argv);  /* optional: remove SDK command line arguments from argv  */

    /* Initialize SDK */
    onesdk_result_t const onesdk_init_result = onesdk_initialize();

    /* optional: Set logging callbacks. */
    onesdk_agent_set_warning_callback(mycallback); /* Highly recommended. */
    onesdk_agent_set_verbose_callback(mycallback); /* Recommended for development & debugging. */

    /* ... use SDK ... */

    /* Shut down SDK */
    if (onesdk_init_result == ONESDK_SUCCESS)
        onesdk_shutdown();

    return 0;
}

📕 Reference documentation for initialization and shutdown
📕 Miscellaneous functions

Special considerations for Solaris SPARC

For agents older than 1.173, auto-configuration is not supported for the OneAgent SDK for C/C++ on Solaris SPARC. Thus you must either use onesdk_stub_set_variable before calling initialize, or set certain environment variables. If you use onesdk_stub_process_cmdline_args, you can also use command line options. If you are familiar with manual injection for Apache or Java on Solaris, this may sound familiar to you.

The following options must be set (to specify on the command line, use the set_variable-name but prepend --dt_):

set_variable Environment variable Value
home DT_HOME Your Dynatrace OneAgent installation folder, e.g. /opt/dynatrace/oneagent/.
tenant DT_TENANT The environment ID of your Dynatrace environment.
tenantToken DT_TENANTTOKEN The token that OneAgent uses to connect to Dynatrace Server. Not an API or PaaS token!
server DT_CONNECTION_POINT One or multiple HTTP addresses that represent Dynatrace Servers or ActiveGates.

Please obtain the values of all these variables (except for home / DT_HOME) from $DT_HOME/dynatrace-env.sh. You can also use this script directly to set the environment variables (except DT_HOME), or start your SDK-using application with $DT_HOME/dynatrace-agent<bitness>.sh <executable> <other command line arguments> (but you still need to set home / DT_HOME).

For example, to set home via onesdk_stub_set_variable, do something like onesdk_stub_set_variable(ONESDK_XSTR("home=/opt/dynatrace/oneagent"), 0); in your code OR set the DT_HOME environment variable, OR, if your application calls onesdk_stub_process_cmdline_args, you can pass --dt_home=/opt/dynatrace/oneagent on the command line.

There is an additional option you may want to set: loglevelcon / DT_LOGLEVELCON can be set to none to stop the agent from writing to stderr (see also Troubleshooting).

How to instrument your application

This section gives samples of how to instrument your application for each supported feature. Refer to the documentation for more details.

General notes

The core API for instrumenting your application is the "tracer". Depending on the kind of operation you want to trace, you use one of the onesdk_*tracer_create functions to create a tracer, then potentially set additional information using setter functions before you start the tracer using onesdk_tracer_start. If an error or exception happens, you can capture it using onesdk_tracer_error. Finally, you need to call onesdk_tracer_end to signal the end of the operation and also free up any resources allocated for the tracer.

Whenever you start a tracer (i.e., call onesdk_tracer_start), the tracer becomes a child of the previously active tracer on this thread and the new tracer then becomes the active tracer. You may only end the active tracer. If you do, the tracer that was active before it (its parent) becomes active again. Put another way, tracers must be ended in reverse order of starting them (you can think of this being like HTML tags where you must also close the child tag before you can close the parent tag).

While the tracer's automatic parent-child relationship works very intuitively in most cases, it does not work with asynchronous patterns, where the same thread handles multiple logically separate operations in an interleaved way on the same thread. If you need to instrument such patterns with the SDK, you need to end your tracer before the thread is potentially reused by any other operation (e.g., before yielding to the event loop). To later continue the trace, capture an in-process link before and later resume using the in-process link tracer, as explained in Trace asynchronous activities. This approach is rather awkward and may lead to complex and difficult to interpret traces. If your application makes extensive use of asynchronous patterns of the kind that is difficult to instrument with the SDK, consider using the OpenTelemetry support of Dynatrace instead.

See also:

📕 Reference documentation for common tracer functions

Trace remote calls

You can use the SDK to trace proprietary IPC communication from one process to the other. This will enable you to see full Service Flow, PurePath and Smartscape topology for remoting technologies that Dynatrace is not aware of.

Instrumenting an outgoing remote call:

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_outgoingremotecalltracer_create(
        onesdk_asciistr("remote service method"),
        onesdk_asciistr("logical service name"),
        onesdk_asciistr("deployed service endpoint"),
        ONESDK_CHANNEL_TYPE_TCP_IP,           /* channel type     */
        onesdk_asciistr("localhost:12345")    /* channel endpoint, host/ip:port in case of TCP_IP */ );

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* get byte representation of tag */
    onesdk_size_t byte_tag_size = 0;
    onesdk_tracer_get_outgoing_dynatrace_byte_tag(tracer, NULL, 0, &byte_tag_size);
    unsigned char* byte_tag = NULL;
    if (byte_tag_size != 0) {
        byte_tag = (unsigned char*)malloc(byte_tag_size);
        if (byte_tag != NULL)
            byte_tag_size = onesdk_tracer_get_outgoing_dynatrace_byte_tag(tracer, byte_tag, byte_tag_size, NULL);
    }

    /* ... do the actual remote call (send along `byte_tag` so the other side can continue tracing) ... */

    /* release tag memory */
    free(byte_tag);

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end and release tracer */
    onesdk_tracer_end(tracer);

Instrumenting an incoming remote call:

    unsigned char const* byte_tag = ...;    /* pointer to the byte tag that we received from the caller */
    onesdk_size_t byte_tag_size = ...;      /* size of the byte tag that we received from the caller    */

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_incomingremotecalltracer_create(
        onesdk_asciistr("remote service method"),
        onesdk_asciistr("logical service name"),
        onesdk_asciistr("deployed service endpoint"));

    /* set the tag that we got from the caller */
    if (byte_tag_size != 0)
        onesdk_tracer_set_incoming_dynatrace_byte_tag(tracer, byte_tag, byte_tag_size);

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* ... do the actual work ... */

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end & release tracer */
    onesdk_tracer_end(tracer);

📕 Reference documentation for remote call tracers

Trace SQL based database calls

To trace database requests you need a database info object which stores the information about your database which does not change between individual requests. This will typically be created somewhere in your initialization code (after initializing the SDK):

onesdk_databaseinfo_handle_t db_info_handle = ONESDK_INVALID_HANDLE;

/* ... */

    db_info_handle = onesdk_databaseinfo_create(
        onesdk_asciistr("database name"),   /* the name of the database that you connect to */
        onesdk_asciistr(ONESDK_DATABASE_VENDOR_POSTGRESQL),   /* the type of the database   */
        ONESDK_CHANNEL_TYPE_TCP_IP,         /* channel type     */
        onesdk_asciistr("localhost:12345")  /* channel endpoint */ );

Then you can trace the SQL database requests:

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_databaserequesttracer_create_sql(
        db_info_handle,
        onesdk_asciistr("SELECT foo FROM bar;"));

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* ... perform the database request, consume results ... */

    /* optional: set number of returned rows */
    onesdk_databaserequesttracer_set_returned_row_count(tracer, 42);
    /* optional: set number of round trips between client and database */
    onesdk_databaserequesttracer_set_round_trip_count(tracer, 3);

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end & release tracer */
    onesdk_tracer_end(tracer);

Finally, release the database info object in your cleanup code (before shutting down the SDK):

    onesdk_databaseinfo_delete(db_info_handle);
    db_info_handle = ONESDK_INVALID_HANDLE;

Please note that SQL database traces are only created if they occur within some other SDK trace (e.g. incoming remote call).

📕 Reference documentation for database request tracers

Trace incoming web requests

To trace incoming web requests you first need to create a web application info object which describes your web application:

onesdk_webapplicationinfo_handle_t web_application_info_handle = ONESDK_INVALID_HANDLE;

/* ... */

    web_application_info_handle = onesdk_webapplicationinfo_create(
        onesdk_asciistr("example.com"),         /* name of the web server that hosts your application */
        onesdk_asciistr("MyWebApplication"),    /* unique name for your web application               */
        onesdk_asciistr("/my-web-app/")         /* context root of your web application               */ );

Then you can trace incoming web requests:

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_incomingwebrequesttracer_create(
        web_application_info_handle,
        onesdk_asciistr("/my-web-app/content.html?q1=1&q2=2#frag"),
        onesdk_asciistr("GET"));

    /* add information about the incoming request */
    onesdk_incomingwebrequesttracer_set_remote_address(tracer, onesdk_asciistr("1.2.3.4:56789"));
    onesdk_incomingwebrequesttracer_add_request_header(tracer,
        onesdk_asciistr("Connection"), onesdk_asciistr("keep-alive"));
    onesdk_incomingwebrequesttracer_add_request_header(tracer,
        onesdk_asciistr("Pragma"), onesdk_asciistr("no-cache"));
    /* ... */

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* ... service the web request ... */

    /* add information about the response */
    onesdk_incomingwebrequesttracer_add_response_header(tracer,
        onesdk_asciistr("Transfer-Encoding"), onesdk_asciistr("chunked"));
    onesdk_incomingwebrequesttracer_add_response_header(tracer,
        onesdk_asciistr("Content-Length"), onesdk_asciistr("1234"));
    onesdk_incomingwebrequesttracer_set_status_code(tracer, 200);
    /* ... */

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end & release tracer */
    onesdk_tracer_end(tracer);

And release the web application info object before shutting down the SDK:

    onesdk_webapplicationinfo_delete(web_application_info_handle);
    web_application_info_handle = ONESDK_INVALID_HANDLE;

📕 Reference documentation for incoming web request tracers

➡️ Trace outgoing web requests

Trace outgoing web requests

You can use the SDK to trace web requests sent by your application:

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_outgoingwebrequesttracer_create(
        onesdk_asciistr("http://example.org:1234/my/rest-service/resources?filter=foo"),
        onesdk_asciistr("GET"));

    /* add information about the request we're about to send */
    onesdk_outgoingwebrequesttracer_add_request_header(tracer,
        onesdk_asciistr("Accept-Charset"), onesdk_asciistr("utf-8"));
    onesdk_outgoingwebrequesttracer_add_request_header(tracer,
        onesdk_asciistr("Pragma"), onesdk_asciistr("no-cache"));
    /* ... */

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* get string representation of tag */
    onesdk_size_t string_tag_size = 0;
    onesdk_tracer_get_outgoing_dynatrace_string_tag(tracer, NULL, 0, &string_tag_size);
    char* string_tag = NULL;
    if (string_tag_size != 0) {
        string_tag = (char*)malloc(string_tag_size);
        if (string_tag != NULL)
            string_tag_size = onesdk_tracer_get_outgoing_dynatrace_string_tag(tracer, string_tag, string_tag_size, NULL);
    }

    /* ... actually send the HTTP request, sending along `string_tag` as an HTTP header
           (use the macro `ONESDK_DYNATRACE_HTTP_HEADER_NAME` for the header name),
           receive the reply and decode it ... */

    /* release tag memory */
    free(string_tag);

    /* add information about the response */
    onesdk_outgoingwebrequesttracer_add_response_header(tracer,
        onesdk_asciistr("Transfer-Encoding"), onesdk_asciistr("chunked"));
    onesdk_outgoingwebrequesttracer_add_response_header(tracer,
        onesdk_asciistr("Content-Length"), onesdk_asciistr("1234"));
    onesdk_outgoingwebrequesttracer_set_status_code(tracer, 200);
    /* ... */

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end and release tracer */
    onesdk_tracer_end(tracer);

📕 Reference documentation for outgoing web request tracers

➡️ Trace incoming web requests

Trace asynchronous activities

Many applications schedule work in some asynchronous fashion. Automatic linking of tracers will not work in such scenarios - it can only link to the innermost active tracer on the current thread. To link the asynchronous parts to the currently active tracer, you first have to create an in-process link:

    /* create in-process link */
    onesdk_size_t in_process_link_size = 0;
    onesdk_inprocesslink_create(NULL, 0, &in_process_link_size);
    unsigned char* in_process_link = NULL;
    if (in_process_link_size != 0) {
        in_process_link = (unsigned char*)malloc(in_process_link_size);
        if (in_process_link != NULL)
            in_process_link_size = onesdk_inprocesslink_create(in_process_link, in_process_link_size, NULL);
    }

    /* ... start/queue asynchronous work (send along `in_process_link` so the other side can continue tracing) ... */

    /* release in-process link memory */
    free(in_process_link);

Once you have the in-process link, you can create an in-process link tracer to continue tracing in another thread:

    unsigned char const* in_process_link = ...;  /* pointer to the in-process link */
    onesdk_size_t in_process_link_size = ...;    /* size of the in-process link    */

    /* create in-process link tracer */
    onesdk_tracer_handle_t const tracer = onesdk_inprocesslinktracer_create(
        in_process_link,
        in_process_link_size);

    /* start tracer ("activates" the in-process link) */
    onesdk_tracer_start(tracer);

    /* ... do the work - new tracers started here will be linked to wherever the in-process link was created ... */

    /* end & release tracer ("deactivates" the in-process link) */
    onesdk_tracer_end(tracer);

Note that you can re-use in-process links to create multiple in-process link tracers.

📕 Reference documentation for in-process link functions

Trace messaging

To trace interaction with a messaging system, such as sending and receiving messages from message queues, you need a messaging system info object which stores the information about your messaging system that does not change between individual requests. This will typically be created somewhere in your initialization code (after initializing the SDK):

    onesdk_messagingsysteminfo_handle_t messagingsysteminfo_handle = onesdk_messagingsysteminfo_create(
        onesdk_asciistr(ONESDK_MESSAGING_VENDOR_RABBIT_MQ), // vendor name
        onesdk_asciistr("myqueue"),                         // destination name
        ONESDK_MESSAGING_DESTINATION_TYPE_QUEUE,            // destination type
        ONESDK_CHANNEL_TYPE_TCP_IP,                         // channel type
        onesdk_asciistr("example.com:1234"));               // channel endpoint

Then you can trace sending, receiving and processing of messages.

Tracing the sending of messages is straightforward and works like other tracers:

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_outgoingmessagetracer_create(messagingsysteminfo_handle);

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* get byte representation of tag (an ASCII string representation is also supported) */
    onesdk_size_t byte_tag_size = 0;
    onesdk_tracer_get_outgoing_dynatrace_byte_tag(tracer, NULL, 0, &byte_tag_size);
    unsigned char* byte_tag = NULL;
    if (byte_tag_size != 0) {
        byte_tag = (unsigned char*)malloc(byte_tag_size);
        if (byte_tag != NULL)
            byte_tag_size = onesdk_tracer_get_outgoing_dynatrace_byte_tag(tracer, byte_tag, byte_tag_size, NULL);
    }

    /* ... do the actual message sending (send along `byte_tag` so the other side can continue tracing) ... */
    mymessage_add_header(mymessage, ONESDK_DYNATRACE_MESSAGE_PROPERTY_NAME, byte_tag, byte_tag_size);
    mymessage_send(mymessage);

    /* release tag memory */
    free(byte_tag);

    /* optional: set message ID, if provided by the messaging system */
    onesdk_outgoingmessagetracer_set_vendor_message_id(tracer, onesdk_asciistr(mymessage_get_id_str(mymessage)));

    /* optional: set correlation ID, if you have one (usually application-defined) */
    onesdk_outgoingmessagetracer_set_correlation_id(tracer, onesdk_asciistr(mycorrelationid);

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end and release tracer */
    onesdk_tracer_end(tracer);

For the other side, we distinguish two activities: Receiving the message an processing it. You can (optionally) use a tracer around receiving one or multiple messages if you are interested in the time the actual receiving takes. For the processing of the message, i.e., any business logic that is executed in response to the message content, there is a different tracer. Note that only the tracer for processing messages supports an incoming tag. This is because the incoming tag must be set before starting the tracer and this cannot be done for the receive-tracer, as the tag is normally part of the message content or headers, thus available only after message receipt.

The following example shows using the message receive and process tracers in the recommended combination, i.e., starting the process tracer inside the receive tracer:

    onesdk_tracer_handle_t receive_tracer = onesdk_incomingmessagereceivetracer_create(messagingsysteminfo_handle);

    /* start receive_tracer */
    onesdk_tracer_start(receive_tracer);

    while (/* ... e.g., a message is available in the queue, or only once ... */) {
        /* ... actually receive a message, ... */

        unsigned char const* byte_tag = mymessage_get_header_optional(mymessage, ONESDK_DYNATRACE_MESSAGE_PROPERTY_NAME);
        onesdk_size_t byte_tag_size = ...;      /* size of the byte tag that we received (Note: byte_tag is not null-terminated) */

        /* create process_tracer */
        onesdk_tracer_handle_t const process_tracer = onesdk_incomingmessageprocesstracer_create(messagingsysteminfo_handle);

        /* set the tag that we got from the message */
        if (byte_tag_size != 0)
            onesdk_tracer_set_incoming_dynatrace_byte_tag(process_tracer, byte_tag, byte_tag_size);

        /* optional: set message ID, if provided by the messaging system */
        onesdk_outgoingmessagetracer_set_vendor_message_id(process_tracer, onesdk_asciistr(mymessage_get_id_str(mymessage)));

        /* optional: set correlation ID, if you have one (usually application-defined) */
        onesdk_outgoingmessagetracer_set_correlation_id(process_tracer, onesdk_asciistr(mycorrelationid));

        /* start process_tracer */
        onesdk_tracer_start(process_tracer);

        /* ... do the actual work: process the message content, do something in response, ... */

        /* set error information */
        if (something_went_wrong_with_processing)
            onesdk_tracer_error(process_tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

        /* end & release process_tracer */
        onesdk_tracer_end(process_tracer);
    }
    /* set error information */
    if (something_went_wrong_with_receiving)
        onesdk_tracer_error(receive_tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));
    onesdk_tracer_end(receive_tracer);

Note that currently, the receive tracer will never create a new PurePath, so if it is not started inside another started tracer, nothing will be traced by it (although the message process tracer will still work).

You should not forget to release the messaging system info object in your cleanup code (before shutting down the SDK):

    onesdk_messagingsysteminfo_delete(messagingsysteminfo_handle);
    messagingsysteminfo_handle = ONESDK_INVALID_HANDLE;

📕 Reference documentation for messaging tracers
📗 Documentation on messaging tracers in the specification repository

Trace custom service methods

You can use the SDK to trace custom service methods. A custom service method is a meaningful part of your code that you want to trace but that does not fit any other tracer.

    /* create tracer */
    onesdk_tracer_handle_t const tracer = onesdk_customservicetracer_create(
        onesdk_asciistr("custom service method"),
        onesdk_asciistr("logical service name"));

    /* start tracer */
    onesdk_tracer_start(tracer);

    /* ... actually execute the custom service ... */

    /* set error information */
    if (something_went_wrong)
        onesdk_tracer_error(tracer, onesdk_asciistr("error type"), onesdk_asciistr("error message"));

    /* end and release tracer */
    onesdk_tracer_end(tracer);

📕 Reference documentation for custom service tracers

Add custom request attributes

You can add custom request attributes (key value pairs) to the currently traced service. Those attributes can then be used to e.g. search/filter requests in Dynatrace. To add a custom request attribute, simply call one of the onesdk_customrequestattribute_add_{type} functions:

    /* add simple values */
    onesdk_customrequestattribute_add_integer(onesdk_asciistr("account-id"), 42);
    onesdk_customrequestattribute_add_float(onesdk_asciistr("service-quality"), 0.707106);
    onesdk_customrequestattribute_add_string(onesdk_asciistr("region"), onesdk_asciistr("emea"));

    /* add multiple values with the same key to create a list */
    onesdk_customrequestattribute_add_integer(onesdk_asciistr("account-group"), 1);
    onesdk_customrequestattribute_add_integer(onesdk_asciistr("account-group"), 2);
    onesdk_customrequestattribute_add_integer(onesdk_asciistr("account-group"), 3);

This will add the custom request attributes to the currently traced service. If no tracer is active, the values will be discarded.

📕 Reference documentation for custom request attributes

Retrieve a W3C trace context

This feature allows you to retrieve a W3C TraceContext trace ID and span ID referencing the current PurePath node, as defined in https://www.w3.org/TR/trace-context.

This trace ID and span ID information is not intended for tagging and context-propagation scenarios and primarily designed for log-enrichment use cases. Use onesdk_tracer_get_outgoing_dynatrace_string_tag, onesdk_tracer_set_incoming_dynatrace_string_tag, onesdk_tracer_get_outgoing_dynatrace_byte_tag, onesdk_tracer_set_incoming_dynatrace_byte_tag for tagging traces (see the usage examples elsewhere in this document).

The following example shows how to print the current trace & span ID to stdout in a format that works well with Dynatrace Log Monitoring (see https://www.dynatrace.com/support/help/shortlink/log-monitoring-log-enrichment for more):

/* The context of the active tracer will be printed, so one should be active.
   You can copy & paste from any other sample that starts a tracer. */
onesdk_tracer_start(tracer);

char trace_id[ONESDK_TRACE_ID_BUFFER_SIZE];
char span_id[ONESDK_SPAN_ID_BUFFER_SIZE];

/* A result code is returned, if and only if it is != ONESDK_SUCCESS, both IDs will consist of ASCII zeros only. */
onesdk_tracecontext_get_current(trace_id, sizeof(trace_id), span_id, sizeof(span_id));
fprintf(stderr, "[!dt dt.trace_id=%s,dt.span_id=%s] Some important log info.\n", trace_id, span_id);

📕 Reference documentation for tracecontext

Using the Dynatrace OneAgent SDK with forked child processes (only available on Linux)

Some applications, especially web servers, use a concurrency model that is based on forked child processes. Typically a master process is started which is responsible only for creating and managing child processes by means of forking. The child processes do the real work, for example handling web requests.

The recommended way to use the SDK in such a scenario is as follows: You initialize the SDK in the master using the onesdk_initialize_2 function passing the ONESDK_INIT_FLAG_FORKABLE flag in the flags argument. This way you will not be able to use the SDK in the master process (attempts to do so will be ignored, if applicable with an error code), but all forked child processes will share the same agent. This has a lower overhead, for example the startup of worker processes is not slowed down, and the per-worker memory overhead is reduced.

Diagram color legend

Recommended, simple scenario:

Diagram showing how to pre-initialize the SDK in the parent/master process

Double/daemon forks (i.e., a process forks a single child and then terminates) are also supported, as long as the intermediate child does not use the SDK. If possible, it is better to initialize the SDK only in the intermediate child, as illustrated in the diagram below, but calling onesdk_initialize_2(ONESDK_FORKABLE) in the master's parent process is also supported (take especial care to not use any SDK functions in the master then, not even onesdk_agent_get_current_state).

Diagram showing how to pre-initialize the SDK in the parent/master process and double forking

There are some scenarios where you may not be able to use this feature:

  • You have to use an older SDK or agent version which does not support the onesdk_initialize_2 API, or
  • You have no control over the master process, i.e. are unable to ensure that onesdk_initialize_2 is called before the first fork in the master process.

In these cases, you can use the following workaround: Instead of initializing the SDK in the master process, you initialize it (without the ONESDK_INIT_FLAG_FORKABLE flag) in each worker process. This has the consequence that each process is monitored separately with its own agent. This is not recommended as each per-worker agent will have to establish and maintain its own connection to Dynatrace, which results in higher overhead, especially at startup.

Diagram showing how to initialize the SDK only in the child processes

When you initialize the SDK using onesdk_initialize or without passing ONESDK_INIT_FLAG_FORKABLE to onesdk_initialize_2, you may not use the SDK in forked child processes (attempts to do so will simply do nothing and may return the ONESDK_ERROR_FORK_CHILD error code).

Diagram showing how to pre-initialize the SDK in the parent/master process and double forking

Example for SDK initialization with ONESDK_INIT_FLAG_FORKABLE:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <onesdk/onesdk.h>

onesdk_result_t g_onesdk_init_result = ONESDK_ERROR_NOT_INITIALIZED;

int worker_main();

int main(int argc, char** argv) {
    onesdk_stub_process_cmdline_args(argc, argv, 1);  /* optional: let the SDK process command line arguments   */
    onesdk_stub_strip_sdk_cmdline_args(&argc, argv);  /* optional: remove SDK command line arguments from argv  */

    /* Initialize SDK in forkable mode. */
    g_onesdk_init_result = onesdk_initialize_2(ONESDK_INIT_FLAG_FORKABLE);
    /* Assuming everything went well, the SDK is now in a _parent-initialized_ state. That means child processes
       that this process forks will be able to use the SDK with very little initialization overhead.
       (This process, the master, won't be able to use the SDK though.) */

    /* This process can now proceed and fork some workers, either all at once or on demand ... */

    static size_t const worker_count = 10;
    for (size_t i = 0; i < worker_count; ++i) {
        int const worker_pid = fork();
        if (worker_pid == -1) {
            perror("fork(2) failed");
            return 1;
        } else if (worker_pid == 0) {
            return worker_main();
        } else {
            /* ... remember worker PID/establish communication ... */
        }
    }

    /* ... maybe accept connections, dispatch work to the worker processes, wait(2) for them ... */

    /* ... unitl eventualls it's time to shut down. */

    /* Shut down SDK */
    if (g_onesdk_init_result == ONESDK_SUCCESS)
        onesdk_shutdown();

    return 0;
}

int worker_main() {
    /* Assuming everything went well in the parent process and `g_onesdk_init_result == ONESDK_SUCCESS`,
       this process has inherited the SDK in a _pre-initialized_ state.
       (The initialization state changes from _parent-initialized_ to _pre-initialized_ while forking.)
       That means the SDK initialization will automatically be completed when this process begins to use
       the SDK, e.g. when the first tracer is created... */

    onesdk_tracer_handle_t const tracer = onesdk_sometracer_create(
        onesdk_asciistr("some argument"),
        onesdk_asciistr("some other argument"));

    /* The SDK should now be fully initialized and the tracer should have been created successfully. */

    onesdk_tracer_start(tracer);
    /* ... do the actual work ... */
    onesdk_tracer_end(tracer);

    /* Since we inherited a _pre-initialized_ SDK state we have to call `onesdk_shutdown`. */
    if (g_onesdk_init_result == ONESDK_SUCCESS)
        onesdk_shutdown();
    return 0;
}

As the behavior of the SDK is sometimes complicated to understand with forking, the onesdk_agent_get_fork_state function is provided which can help you examine the state the SDK currently is in regarding forking. Basic usage is shown in sample1. You may want to call it before and after an SDK operation that behaves/fails unexpectly if you use ONESDK_INIT_FLAG_FORKABLE.

📕 Reference documentation for:

Troubleshooting

This section describes two broad categories of problems and how to debug them. The SDK has extensive logging capabilites which will often point you quite directly at what the issue is.

Problems with initializing the SDK

Main symptom: onesdk_initialize / onesdk_initalize_2 returning an error code.

If the SDK stub cannot load or initialize the agent module (see output of sample1), you can set the SDK stub's logging level to activate logging of what happengs during initialization by either

Whichever method you choose (usually setting the environment variable is the easiest), make sure to apply it before calling onesdk_initialize / onesdk_initalize_2.

Once you have enabled logging, log output of the stub will be written to stderr by default. Refer to the documentation for onesdk_stub_set_logging_callback if you need to process stub log messages in another way.

If initialization fails, ONESDK_ERROR_LOAD_AGENT (numerical code 2952658951, -1342308345 or 0xaffe0007, error message "Could not load agent."). is the most common error code. These are the two most common causes we have observed for this issue:

  1. The OneAgent is not installed on the host where the program runs. Install the OneAgent and restart the program.
  2. The program being run is started with a debugger. The OneAgent will not inject in that case. Start the program without debugger. You may still attach the debugger later, once the program is running.

Problems occuring after initialization

For any problems you encounter after successful initialization (for example, no paths are shown in the UI, or you wonder why a function returns ONESDK_INVALID_HANDLE or another error code), it is best to check for messages from the agent logging callbacks: see onesdk_agent_set_warning_callback and onesdk_agent_set_verbose_callback in the reference documentation or in sample1.

You can also check the agent log files (see the Dynatrace documentation for where to find them, e.g., on Linux or Windows). You can increase the agent log level by setting the environment variable DT_LOGLEVELFILE={level} or passing the command line argument --dt_loglevelfile={level} to the SDK. This will provide additional debug information in agent log file. (Alternatively you can use DT_LOGLEVELCON={level} or --dt_loglevelcon={level} if you want to receive agent log output via stderr.)

Lastly, in some situations, the onesdk_agent_get_current_state function may provide additional insights. See sample1 for a usage example.

Special situations can arise when forking is involved, see the section on forking.

Requirements

  • Dynatrace OneAgent needs to be installed on the system that is to be monitored (required versions see below)

  • The supported environments are:

    • Windows on x86
    • Linux on x86. musl libc is currently not supported (e.g. used in Alpine Linux)
    • Solaris on SPARC (x86 is not supported). Support for this platform is new since version 1.3.2 of the SDK.

    Refer to https://www.dynatrace.com/support/help/shortlink/supported-technologies#operating-systems for the exact versions supported by OneAgent. Note that only the subset of operating systems and processor architectures explicitly listed both in this README and the link are supported by the SDK.

Version support and compatibility table

OneAgent SDK for C/C++ Dynatrace OneAgent Support status or EOL date
1.7.1 >=1.251 Supported
1.6.1 >=1.179 Supported
1.5.1 >=1.179 Deprecated with support ending 2023-07-01
1.4.1 >=1.161 Deprecated with support ending 2023-07-01
1.3.2 >=1.159 Deprecated with support ending 2023-07-01
1.3.1 >=1.151 Deprecated with support ending 2023-07-01
1.2.0 >=1.147 Deprecated with support ending 2023-07-01
1.1.0 >=1.141 Deprecated with support ending 2023-07-01
1.0.0 >=1.133 Deprecated with support ending 2023-07-01

Note that this table only states the support status of the mentioned OneAgent SDK for C/C++ version, not the OneAgent itself.

You should always try to update to the latest version if possible, as it may contain reliability or security improvements (see respective release notes). This is especially true if an end of support date for your version is announced.

Help & Support

The Dynatrace OneAgent SDK for C/C++ is fully supported by Dynatrace. For the support status of a particular version, refer to the version support and compatibility table. For detailed support policy see Dynatrace OneAgent SDK help.

Read the manual

Let us help you

Get Help

Open a GitHub issue to:

  • Report minor defects like typos
  • Ask any questions related to the community effort

SLAs don't apply for GitHub tickets.

Customers can open a ticket on the Dynatrace support portal to:

  • Get support from the Dynatrace technical support engineering team
  • Manage and resolve product related technical issues

SLAs apply according to the customer's support level.

Release Notes

See also https://github.com/Dynatrace/OneAgent-SDK-for-C/releases.

Version Description
1.7.1 Add W3C trace context support for log enrichment (not for tagging/linking).
Announce deprecation of versions < 1.6.1
1.6.1 Deprecate metrics-related APIs.
Don't look for agent module in PATH/LD_LIBRARY_PATH/..., disallow relative DT_HOME on Windows (prevent DLL hijacking)
1.5.1 Added metrics APIs (preview feature), improved logging callback APIs, new API to query fork state
1.4.1 Added custom service tracers and messaging tracers
1.3.2 Support for Solaris SPARC
1.3.1 Support for monitoring forked child processes, added new API to check agent version compatibility
1.2.0 Added in-process linking, added custom request attributes, added outgoing web request tracers
1.1.0 Added incoming web request tracers, added row count & round trip count for DB request tracers
1.0.0 Initial version