diff --git a/.vscode/cspell.json b/.vscode/cspell.json index b522f7d9c6..1799bc96c4 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -42,9 +42,11 @@ "cuse", "CUSEUAP", "DCMAKE", + "DDISABLE", "deserializers", "Deserializes", "DFETCH", + "DMSVC", "docfx", "DPAPI", "DRUN", @@ -81,6 +83,7 @@ "ncus", "Niels", "nlohmann", + "nostd", "noclean", "NOCLOSE", "NOCRLF", @@ -89,6 +92,7 @@ "northcentralus", "NTSTATUS", "okhttp", + "otel", "PBYTE", "pdbs", "Piotrowski", diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b64d46954..391c771aa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,21 +55,31 @@ if(MSVC_USE_STATIC_CRT AND MSVC) # # 5. We "replace with empty string" (i.e. remove) first, then add, so that '/MT' # will be present (and present once) even if '/MD' was not. - + message(STATUS "Configuring Static Runtime Library.") + if(${CMAKE_CXX_FLAGS} MATCHES ".*/MD.*") string(REGEX REPLACE "/MD" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") + endif() + if(${CMAKE_CXX_FLAGS_RELEASE} MATCHES ".*/MD.*") string(REGEX REPLACE "/MD" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") - + endif() + + if(${CMAKE_CXX_FLAGS_RELWITHDEBINFO} MATCHES ".*/MD.*") string(REGEX REPLACE "/MD" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MT") + endif() + if(${CMAKE_CXX_FLAGS_MINSIZEREL} MATCHES ".*/MD.*") string(REGEX REPLACE "/MD" "" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /MT") + endif() + if(${CMAKE_CXX_FLAGS_DEBUG} MATCHES ".*/MD.*") string(REGEX REPLACE "/MDd" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + endif() endif() if(BUILD_TESTING) diff --git a/CMakeSettings.json b/CMakeSettings.json index 6141da95da..445a2b6952 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -15,7 +15,13 @@ "name": "VCPKG_TARGET_TRIPLET", "value": "x64-windows-static", "type": "STRING" + }, + { + "name": "MSVC_USE_STATIC_CRT", + "value": "True", + "type": "BOOL" } + ] }, { @@ -33,6 +39,11 @@ "name": "VCPKG_TARGET_TRIPLET", "value": "x64-windows-static", "type": "STRING" + }, + { + "name": "MSVC_USE_STATIC_CRT", + "value": "True", + "type": "BOOL" } ] }, @@ -51,6 +62,11 @@ "name": "VCPKG_TARGET_TRIPLET", "value": "x86-windows-static", "type": "STRING" + }, + { + "name": "MSVC_USE_STATIC_CRT", + "value": "True", + "type": "BOOL" } ] }, @@ -70,6 +86,11 @@ "value": "True", "type": "BOOL" }, + { + "name": "MSVC_USE_STATIC_CRT", + "value": "True", + "type": "BOOL" + }, { "name": "BUILD_TRANSPORT_CURL", "value": "True", diff --git a/eng/pipelines/templates/stages/platform-matrix.json b/eng/pipelines/templates/stages/platform-matrix.json index 090818cc9b..15571ad469 100644 --- a/eng/pipelines/templates/stages/platform-matrix.json +++ b/eng/pipelines/templates/stages/platform-matrix.json @@ -44,15 +44,15 @@ "CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON" }, "Win32Api_release_curl": { - "CmakeArgs": " -DBUILD_TESTING=ON -DBUILD_PERFORMANCE_TESTS=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_TRANSPORT_CURL=ON", + "CmakeArgs": " -DBUILD_TESTING=ON -DBUILD_PERFORMANCE_TESTS=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_TRANSPORT_CURL=ON -DMSVC_USE_STATIC_CRT=ON", "BuildArgs": "--parallel 8 --config Release", "PublishMapFiles": "true" - }, + }, "Win32Api_debug_tests": { - "CmakeArgs": " -DBUILD_TESTING=ON -DBUILD_PERFORMANCE_TESTS=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_TRANSPORT_CURL=ON -DBUILD_TRANSPORT_WINHTTP=ON", + "CmakeArgs": " -DBUILD_TESTING=ON -DBUILD_PERFORMANCE_TESTS=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_TRANSPORT_CURL=ON -DBUILD_TRANSPORT_WINHTTP=ON -DMSVC_USE_STATIC_CRT=ON", "BuildArgs": "--parallel 8 --config Debug", "PublishMapFiles": "true" - } + } }, "TargetArchitecture": { "x86": { @@ -72,7 +72,6 @@ "OSVmImage": "MMS2019", "Pool": "azsdk-pool-mms-win-2019-general", "CMAKE_GENERATOR": "Visual Studio 16 2019", - "CmakeArgs": " -DBUILD_TRANSPORT_WINHTTP=ON ", "PublishMapFiles": "true" } }, @@ -80,11 +79,13 @@ "UWP_debug": { "CMAKE_SYSTEM_NAME": "WindowsStore", "CMAKE_SYSTEM_VERSION": "10.0", + "CmakeArgs": " -DBUILD_TRANSPORT_WINHTTP=ON -DDISABLE_AZURE_CORE_OPENTELEMETRY=ON ", "BuildArgs": "--parallel 8 --config Debug" }, "UWP_release": { "CMAKE_SYSTEM_NAME": "WindowsStore", "CMAKE_SYSTEM_VERSION": "10.0", + "CmakeArgs": " -DBUILD_TRANSPORT_WINHTTP=ON -DDISABLE_AZURE_CORE_OPENTELEMETRY=ON ", "BuildArgs": "--parallel 8 --config Release" } }, @@ -97,12 +98,12 @@ }, { "StaticConfigs": { - "Ubuntu18": { - "OSVmImage": "MMSUbuntu18.04", - "Pool": "azsdk-pool-mms-ubuntu-1804-general", - "VCPKG_DEFAULT_TRIPLET": "x64-linux", - "BuildArgs": "-j 10" - } + "Ubuntu18": { + "OSVmImage": "MMSUbuntu18.04", + "Pool": "azsdk-pool-mms-ubuntu-1804-general", + "VCPKG_DEFAULT_TRIPLET": "x64-linux", + "BuildArgs": "-j 10" + } }, "BuildSettings": { "gpp-5": { diff --git a/eng/pipelines/templates/steps/vcpkg.yml b/eng/pipelines/templates/steps/vcpkg.yml index 687bac23b3..b442b76581 100644 --- a/eng/pipelines/templates/steps/vcpkg.yml +++ b/eng/pipelines/templates/steps/vcpkg.yml @@ -31,3 +31,4 @@ steps: -Ref $(VcpkgCommit) -VcpkgPath $(VCPKG_INSTALLATION_ROOT) pwsh: true + displayName: Clone Vcpkg. diff --git a/sdk/core/CMakeLists.txt b/sdk/core/CMakeLists.txt index 3c3e8c2891..30d6e9ee9d 100644 --- a/sdk/core/CMakeLists.txt +++ b/sdk/core/CMakeLists.txt @@ -9,6 +9,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_subdirectory(azure-core) +if (NOT DISABLE_AZURE_CORE_OPENTELEMETRY) + add_subdirectory(azure-core-tracing-opentelemetry) +endif() + if (BUILD_PERFORMANCE_TESTS) add_subdirectory(perf) endif() diff --git a/sdk/core/azure-core-tracing-opentelemetry/CHANGELOG.md b/sdk/core/azure-core-tracing-opentelemetry/CHANGELOG.md new file mode 100644 index 0000000000..0877171bac --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History + +## 1.0.0-beta.1 (Unreleased) + +- Initial release diff --git a/sdk/core/azure-core-tracing-opentelemetry/CMakeLists.txt b/sdk/core/azure-core-tracing-opentelemetry/CMakeLists.txt new file mode 100644 index 0000000000..e0dfb353de --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/CMakeLists.txt @@ -0,0 +1,95 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +# setting CMAKE_TOOLCHAIN_FILE must happen before creating the project +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake-modules") +include(AzureVcpkg) +az_vcpkg_integrate() + +# Azure core is compatible with CMake 3.12 +cmake_minimum_required (VERSION 3.12) +project(azure-core-tracing-opentelemetry LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + +include(AzureVersion) +include(AzureCodeCoverage) +include(AzureTransportAdapters) +include(AzureDoxygen) +include(AzureGlobalCompileOptions) +include(AzureConfigRTTI) +# Add create_map_file function +include(CreateMapFile) + +find_package(Threads REQUIRED) + +if(NOT AZ_ALL_LIBRARIES) + find_package(azure-core-cpp "1.5.0" CONFIG QUIET) + if(NOT azure-core-cpp_FOUND) + find_package(azure-core-cpp "1.5.0" REQUIRED) + endif() +endif() +find_package(opentelemetry-cpp "1.3.0" CONFIG REQUIRED) + +set( + AZURE_CORE_OPENTELEMETRY_HEADER + inc/azure/core/tracing/opentelemetry/opentelemetry.hpp +) + +set( + AZURE_CORE_OPENTELEMETRY_SOURCE + src/opentelemetry.cpp +) + +add_library(azure-core-tracing-opentelemetry ${AZURE_CORE_OPENTELEMETRY_HEADER} ${AZURE_CORE_OPENTELEMETRY_SOURCE}) + +target_include_directories( + azure-core-tracing-opentelemetry + PUBLIC + $ + $ +) + +# make sure that users can consume the project as a library. +add_library(Azure::azure-core-tracing-opentelemetry ALIAS azure-core-tracing-opentelemetry) + +# coverage. Has no effect if BUILD_CODE_COVERAGE is OFF +create_code_coverage(core-tracing-opentelemetry azure-core-tracing-opentelemetry azure-core-tracing-opentelemetry-test "tests?/*;samples?/*") + +target_link_libraries(azure-core-tracing-opentelemetry INTERFACE Threads::Threads) + +target_link_libraries(azure-core-tracing-opentelemetry PRIVATE azure-core + opentelemetry-cpp::api + opentelemetry-cpp::ext + opentelemetry-cpp::sdk + opentelemetry-cpp::trace) + + +get_az_version("${CMAKE_CURRENT_SOURCE_DIR}/src/private/package_version.hpp") +generate_documentation(azure-core-opentelemetry ${AZ_LIBRARY_VERSION}) + +az_vcpkg_export( + azure-core-tracing-opentelemetry + CORE_TRACING_OPENTELEMETRY + "azure/core/tracing/opentelemetry/dll_import_export.hpp" + ) + +az_rtti_setup( + azure-core-tracing-opentelemetry + CORE_TRACING_OPENTELEMETRY + "azure/core/tracing/opentelemetry/rtti.hpp" +) + +if(BUILD_TESTING) + # define a symbol that enables some test hooks in code + add_compile_definitions(TESTING_BUILD) + + if (NOT AZ_ALL_LIBRARIES) + include(AddGoogleTest) + enable_testing () + endif() + + add_subdirectory(test/ut) +endif() diff --git a/sdk/core/azure-core-tracing-opentelemetry/NOTICE.txt b/sdk/core/azure-core-tracing-opentelemetry/NOTICE.txt new file mode 100644 index 0000000000..4ea16fae65 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/NOTICE.txt @@ -0,0 +1,269 @@ +azure-core-tracing-opentelemetry + +NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. Microsoft makes certain +open source code available at https://3rdpartysource.microsoft.com, or you may +send a check or money order for US $5.00, including the product name, the open +source component name, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the +extent required to debug changes to any libraries licensed under the GNU Lesser +General Public License. + +------------------------------------------------------------------------------ + +Azure SDK for C++ uses third-party libraries or other resources that may be +distributed under licenses different than the Azure SDK for C++ software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + azcppsdkhelp@microsoft.com + +The attached notices are provided for information only. + + +License notice for CMake Modules AddGoogleTest +------------------------------------------------------------------- + +BSD 3-Clause License + +Copyright (c) 2017, University of Cincinnati +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Licence notice for opentelemetry-cpp +------------------------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/sdk/core/azure-core-tracing-opentelemetry/README.md b/sdk/core/azure-core-tracing-opentelemetry/README.md new file mode 100644 index 0000000000..64c6c4befc --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/README.md @@ -0,0 +1,141 @@ +# Azure SDK Core Tracing Library for C++ + +Azure::Core::Tracing::OpenTelemetry (`azure-core-tracing-opentelemetry`) provides an implementation +to enable customers to implement tracing in the Azure SDK for C++ libraries. + +## Getting started + +### Include the package + +The easiest way to acquire the OpenTelemetry library is leveraging vcpkg package manager. See the corresponding [Azure SDK for C++ readme section][azsdk_vcpkg_install]. + +To install Azure Core OpenTelemetry package via vcpkg: + +```cmd +> vcpkg install azure-core-tracing-opentelemetry-cpp +``` + +Then, use in your CMake file: + +```CMake +find_package(azure-core-tracing-opentelemetry-cpp CONFIG REQUIRED) +target_link_libraries( PRIVATE Azure::azure-core-opentelemetry) +``` + +## Key concepts + +The `azure-core-tracing-opentelemetry` package supports enabling tracing for Azure SDK packages, using an [OpenTelemetry](https://opentelemetry.io/) `Tracer`. + +By default, all libraries log with a `NoOpTracer` that takes no action. To enable tracing, you will need to set a global tracer provider following the instructions in the [OpenTelemetry getting started guide](https://opentelemetry-cpp.readthedocs.io/en/latest/api/GettingStarted.html) or the [Enabling Tracing using OpenTelemetry example](#enabling-tracing-using-opentelemetry) below. + +### Span Propagation + +Core Tracing supports both automatic and manual span propagation. Automatic propagation is handled using OpenTelemetry's API and will work well in most scenarios. + +For customers who require manual propagation, all client library operations accept an optional field in the `options` parameter where a tracingContext can +be passed in and used as the currently active context. Please see the [Manual Span Propagation example](#manual-span-propagation-using-opentelemetry) +below for more details. + +### OpenTelemetry Compatibility + +Most Azure SDKs use [OpenTelemetry](https://opentelemetry.io/) to support tracing. Specifically, we depend on +the [azure-core-opentelemetry](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/docs/building-with-vcpkg.md) VCPKG package. + + +## Examples + +### Enabling tracing using OpenTelemetry + +```cpp +// Start by creating an OpenTelemetry Provider using the +// default OpenTelemetry tracer provider. +std::shared_ptr tracerProvider = std::make_shared(); + +// Connect the tracerProvider to the current application context. +ApplicationContext().SetTracerProvider(tracerProvider); +``` + +After this, the SDK API implementations will be able to retrieve the tracer provider and produce tracing events automatically. + +### Enabling tracing using a non-default TracerProvider + +```cpp +// Start by creating an OpenTelemetry Provider. +auto exporter = std::make_unique(); +m_spanData = exporter->GetData(); + +// simple processor +auto simple_processor = std::unique_ptr( + new opentelemetry::sdk::trace::SimpleSpanProcessor(std::move(exporter))); + +auto always_on_sampler = std::unique_ptr( + new opentelemetry::sdk::trace::AlwaysOnSampler); + +auto resource_attributes = opentelemetry::sdk::resource::ResourceAttributes{ + {"service.name", "telemetryTest"}, {"service.instance.id", "instance-1"}}; +auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); +auto openTelemetryProvider = opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::trace::TracerProvider( + std::move(simple_processor), resource, std::move(always_on_sampler))); + +// Use the default OpenTelemetry tracer provider. +std::shared_ptr tracerProvider = + std::make_shared(openTelemetryProvider); + +// Connect the tracerProvider to the current application context. +ApplicationContext().SetTracerProvider(tracerProvider); +``` + +### Manual Span Propagation using OpenTelemetry + +In Azure Service methods, the `Azure::Context` value passed into the tracer optionally has an associated Span. + +If there is a span associated with the `Azure::Context`, then calling `DiagnosticTracingFactory::CreateSpanFromContext` will +cause a new span to be created using the span in the provided `Azure::Context` object as the parent span. + +```cpp + auto contextAndSpan + = Azure::Core::Tracing::_internal::DiagnosticTracingFactory::CreateSpanFromContext( + "HTTP GET#2", context); +``` + + +## Next steps + +You can build and run the tests locally by executing `azure-core-tracing-opentelemetry-test`. Explore the `test` folder to see advanced usage and behavior of the public classes. + +## Troubleshooting + +If you run into issues while using this library, please feel free to [file an issue](https://github.com/Azure/azure-sdk-for-cpp/issues/new). + +### OpenTelemetry Compatibility Errors + + +> Ideally you'd want to use OpenTelemetry 1.3.0 or higher. + + + +### Reporting security issues and security bugs + +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) . You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://www.microsoft.com/msrc/faqs-report-an-issue). + +### License + +Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-cpp/blob/main/LICENSE.txt) license. + + +[azsdk_vcpkg_install]: https://github.com/Azure/azure-sdk-for-cpp#download--install-the-sdk +[azure_sdk_for_cpp_contributing]: https://github.com/Azure/azure-sdk-for-cpp/blob/main/CONTRIBUTING.md +[azure_sdk_for_cpp_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-cpp/blob/main/CONTRIBUTING.md#developer-guide +[azure_sdk_for_cpp_contributing_pull_requests]: https://github.com/Azure/azure-sdk-for-cpp/blob/main/CONTRIBUTING.md#pull-requests +[azure_sdk_cpp_development_guidelines]: https://azure.github.io/azure-sdk/cpp_introduction.html +[azure_cli]: https://docs.microsoft.com/cli/azure +[azure_pattern_circuit_breaker]: https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker +[azure_pattern_retry]: https://docs.microsoft.com/azure/architecture/patterns/retry +[azure_portal]: https://portal.azure.com +[azure_sub]: https://azure.microsoft.com/free/ +[c_compiler]: https://visualstudio.microsoft.com/vs/features/cplusplus/ +[cloud_shell]: https://docs.microsoft.com/azure/cloud-shell/overview +[cloud_shell_bash]: https://shell.azure.com/bash + +![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-cpp%2Fsdk%2Fcore%2Fcore-opentelemetry%2FREADME.png) \ No newline at end of file diff --git a/sdk/core/azure-core-tracing-opentelemetry/cgmanifest.json b/sdk/core/azure-core-tracing-opentelemetry/cgmanifest.json new file mode 100644 index 0000000000..698c2a9558 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/cgmanifest.json @@ -0,0 +1,23 @@ +{ + "Registrations": [ + { + "Component": { + "Type": "git", + "git": { + "RepositoryUrl": "https://github.com/open-telemetry/opentelemetry-cpp", + "CommitHash": "da2911cf4458c7068f967c17c65d07eeba449a08" + } + } + }, + { + "Component": { + "Type": "git", + "git": { + "RepositoryUrl": "https://github.com/google/googletest", + "CommitHash": "703bd9caab50b139428cea1aaff9974ebee5742e" + } + }, + "DevelopmentDependency": true + } + ] +} diff --git a/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/dll_import_export.hpp b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/dll_import_export.hpp new file mode 100644 index 0000000000..6a32a496e1 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/dll_import_export.hpp @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief DLL export macro. + */ + +// For explanation, see the comment in azure/core/dll_import_export.hpp + +#pragma once + +/** + * @def AZ_CORE_TRACING_OPENTELEMETRY_DLLEXPORT + * @brief Applies DLL export attribute, when applicable. + * @note See https://docs.microsoft.com/cpp/cpp/dllexport-dllimport?view=msvc-160. + */ + +#if defined(AZ_CORE_TRACING_OPENTELEMETRY_DLL) \ + || (0 /*@AZ_CORE_TRACING_OPENTELEMETRY_DLL_INSTALLED_AS_PACKAGE@*/) +#define AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL 1 +#else +#define AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL 0 +#endif + +#if AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL +#if defined(_MSC_VER) +#if defined(AZ_CORE_TRACING_OPENTELEMETRY_BEING_BUILT) +#define AZ_CORE_TRACING_OPENTELEMETRY_DLLEXPORT __declspec(dllexport) +#else // !defined(AZ_CORE_TRACING_OPENTELEMETRY_BEING_BUILT) +#define AZ_CORE_TRACING_OPENTELEMETRY_DLLEXPORT __declspec(dllimport) +#endif // AZ_CORE_TRACING_OPENTELEMETRY_BEING_BUILT +#else // !defined(_MSC_VER) +#define AZ_CORE_TRACING_OPENTELEMETRY_DLLEXPORT +#endif // _MSC_VER +#else // !AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL +#define AZ_CORE_TRACING_OPENTELEMETRY_DLLEXPORT +#endif // AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL + +#undef AZ_CORE_TRACING_OPENTELEMETRY_BUILT_AS_DLL + +/** + * @brief Azure SDK abstractions. + * + */ +namespace Azure { +/** + * @brief Abstractions commonly used by Azure SDK client libraries. + * + */ +namespace Core { + /** @brief Azure Tracing Abstractions + */ + namespace Tracing { + /** @brief OpenTelemetry Tracing Abstractions + */ + namespace OpenTelemetry { + } + } // namespace Tracing +} // namespace Core +} // namespace Azure diff --git a/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/opentelemetry.hpp b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/opentelemetry.hpp new file mode 100644 index 0000000000..e840cfd2d4 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/opentelemetry.hpp @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#if defined(_MSC_VER) +// The OpenTelemetry headers generate a couple of warnings on MSVC in the OTel 1.2 package, suppress +// the warnings across the includes. +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4244) +#endif +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +namespace Azure { namespace Core { namespace Tracing { namespace OpenTelemetry { + + namespace _detail { + class OpenTelemetryAttributeSet final : public Azure::Core::Tracing::_internal::AttributeSet, + public opentelemetry::common::KeyValueIterable { + std::map m_propertySet; + + template void AddAttributeToSet(std::string const& attributeName, T value) + { + m_propertySet.emplace( + std::make_pair(attributeName, opentelemetry::common::AttributeValue(value))); + } + + public: + void AddAttribute(std::string const& attributeName, int32_t value) override + { + AddAttributeToSet(attributeName, value); + } + + void AddAttribute(std::string const& attributeName, int64_t value) override + { + AddAttributeToSet(attributeName, value); + } + + void AddAttribute(std::string const& attributeName, uint64_t value) override + { + AddAttributeToSet(attributeName, value); + } + void AddAttribute(std::string const& attributeName, double value) override + { + AddAttributeToSet(attributeName, value); + } + + void AddAttribute(std::string const& attributeName, std::string const& value) override + { + AddAttributeToSet(attributeName, value); + } + void AddAttribute(std::string const& attributeName, const char* value) override + { + AddAttributeToSet(attributeName, value); + } + + void AddAttribute(std::string const& attributeName, bool value) override + { + AddAttributeToSet(attributeName, value); + } + + /** + * Iterate over key-value pairs + * @param callback a callback to invoke for each key-value. If the callback returns false, + * the iteration is aborted. + * @return true if every key-value pair was iterated over + */ + bool ForEachKeyValue( + opentelemetry::nostd::function_ref< + bool(opentelemetry::nostd::string_view, opentelemetry::common::AttributeValue)> + callback) const noexcept override + { + for (auto& value : m_propertySet) + { + if (!callback(value.first, value.second)) + { + return false; + } + } + return true; + } + + /** + * @return the number of key-value pairs + */ + size_t size() const noexcept override { return m_propertySet.size(); } + + ~OpenTelemetryAttributeSet() {} + }; + /** + * @brief Span - represents a span in tracing. + */ + class OpenTelemetrySpan final : public Azure::Core::Tracing::_internal::Span { + opentelemetry::nostd::shared_ptr m_span; + + public: + OpenTelemetrySpan(opentelemetry::nostd::shared_ptr span); + + ~OpenTelemetrySpan(); + + /** + * @brief Signals that the span has now ended. + */ + virtual void End(Azure::Nullable endTime) override; + + virtual void AddAttributes( + Azure::Core::Tracing::_internal::AttributeSet const& attributeToAdd) override; + + /** + * Add an Event to the span. An event is identified by a name and an optional set of + * attributes associated with the event. + */ + virtual void AddEvent( + std::string const& eventName, + Azure::Core::Tracing::_internal::AttributeSet const& eventAttributes) override; + virtual void AddEvent(std::string const& eventName) override; + virtual void AddEvent(std::exception const& exception) override; + + virtual void SetStatus( + Azure::Core::Tracing::_internal::SpanStatus const& status, + std::string const& statusMessage) override; + + opentelemetry::trace::SpanContext GetContext() { return m_span->GetContext(); } + }; + + class OpenTelemetryTracer final : public Azure::Core::Tracing::_internal::Tracer { + opentelemetry::nostd::shared_ptr m_tracer; + + public: + OpenTelemetryTracer(opentelemetry::nostd::shared_ptr tracer); + std::shared_ptr CreateSpan( + std::string const& spanName, + Azure::Core::Tracing::_internal::CreateSpanOptions const& options) const override; + + std::unique_ptr CreateAttributeSet() + const override; + }; + } // namespace _detail + + /** + * @brief Trace Provider - factory for creating Tracer objects. + * + * An OpenTelemetryProvider object wraps an opentelemetry-cpp TracerProvider object + * and provides an abstraction of the opentelemetry APIs which can be consumed by Azure Core and + * other Azure services. + * + */ + class OpenTelemetryProvider final : public Azure::Core::Tracing::TracerProvider { + opentelemetry::nostd::shared_ptr m_tracerProvider; + + public: + OpenTelemetryProvider( + opentelemetry::nostd::shared_ptr tracerProvider); + OpenTelemetryProvider(); + + /** + * @brief Create a Tracer object + * + * @param name Name of the tracer object, typically the name of the Service client + * (Azure.Storage.Blobs, for example) + * @param version Version of the service client. + * @return std::shared_ptr + */ + virtual std::shared_ptr CreateTracer( + std::string const& name, + std::string const& version = "") const override; + }; +}}}} // namespace Azure::Core::Tracing::OpenTelemetry diff --git a/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/rtti.hpp b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/rtti.hpp new file mode 100644 index 0000000000..214ef2f517 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/inc/azure/core/tracing/opentelemetry/rtti.hpp @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Run-time type info enable or disable. + * + * @details When RTTI is enabled, defines a macro `AZ_CORE_RTTI`. When + * the macro is not defined, RTTI is disabled. + * + * @details Each library has this header file. These headers are being configured by + * `az_rtti_setup()` CMake macro. CMake install will patch this file during installation, depending + * on the build flags. + */ + +#pragma once + +/** + * @def AZ_CORE_TRACING_OPENTELEMETRY_RTTI + * @brief A macro indicating whether the code is built with RTTI or not. + * + * @details `AZ_RTTI` could be defined while building the Azure SDK with CMake, however, after + * the build is completed, that information is not preserved for the code that consumes Azure SDK + * headers, unless the code that consumes the SDK is the part of the same build process. To address + * this issue, CMake install would patch the header it places in the installation directory, so that + * condition: + * `#if defined(AZ_RTTI) || (0)` + * becomes, effectively, + * `#if defined(AZ_RTTI) || (0 + 1)` + * when the library was built with RTTI support, and will make no changes to the + * condition when it was not. + */ + +#if defined(AZ_RTTI) || (0 /*@AZ_CORE_TRACING_OPENTELEMETRY_RTTI@*/) +#define AZ_CORE_TRACING_OPENTELEMETRY_RTTI +#endif diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/opentelemetry.cpp b/sdk/core/azure-core-tracing-opentelemetry/src/opentelemetry.cpp new file mode 100644 index 0000000000..8f0cd0b480 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/src/opentelemetry.cpp @@ -0,0 +1,189 @@ + +#include "azure/core/tracing/opentelemetry/opentelemetry.hpp" +#include +#include +#include +#if defined(_MSC_VER) +// The OpenTelemetry headers generate a couple of warnings on MSVC in the OTel 1.2 package, suppress +// the warnings across the includes. +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4244) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +namespace Azure { namespace Core { namespace Tracing { namespace OpenTelemetry { + using namespace Azure::Core::Tracing::_internal; + + OpenTelemetryProvider::OpenTelemetryProvider( + opentelemetry::nostd::shared_ptr tracerProvider) + : m_tracerProvider(tracerProvider) + { + } + + OpenTelemetryProvider::OpenTelemetryProvider() + : m_tracerProvider(opentelemetry::trace::Provider::GetTracerProvider()) + { + } + + std::shared_ptr OpenTelemetryProvider::CreateTracer( + std::string const& name, + std::string const& version) const + { + opentelemetry::nostd::shared_ptr returnTracer( + m_tracerProvider->GetTracer(name, version)); + return std::make_shared( + returnTracer); + } + namespace _detail { + std::unique_ptr + OpenTelemetryTracer::CreateAttributeSet() const + { + return std::make_unique(); + } + + OpenTelemetryTracer::OpenTelemetryTracer( + opentelemetry::nostd::shared_ptr tracer) + : m_tracer(tracer) + { + } + + std::shared_ptr OpenTelemetryTracer::CreateSpan( + std::string const& spanName, + Azure::Core::Tracing::_internal::CreateSpanOptions const& options = {}) const + { + opentelemetry::trace::StartSpanOptions spanOptions; + spanOptions.kind = opentelemetry::trace::SpanKind::kInternal; + if (options.Kind == Azure::Core::Tracing::_internal::SpanKind::Client) + { + spanOptions.kind = opentelemetry::trace::SpanKind::kClient; + } + else if (options.Kind == SpanKind::Consumer) + { + spanOptions.kind = opentelemetry::trace::SpanKind::kConsumer; + } + else if (options.Kind == SpanKind::Producer) + { + spanOptions.kind = opentelemetry::trace::SpanKind::kProducer; + } + else if (options.Kind == SpanKind::Server) + { + spanOptions.kind = opentelemetry::trace::SpanKind::kServer; + } + else if (options.Kind == SpanKind::Internal) + { + spanOptions.kind = opentelemetry::trace::SpanKind::kInternal; + } + else + { + throw std::runtime_error("Unknown SpanOptions Kind: " + options.Kind.ToString()); + } + + if (options.ParentSpan) + { + spanOptions.parent + = static_cast(options.ParentSpan.get())->GetContext(); + } + + opentelemetry::nostd::shared_ptr newSpan; + if (options.Attributes) + { + // Note: We make a huge assumption here: That if you're calling into the OpenTelemetry + // version of Azure::Core::Tracing, the Attributes passed in will be an + // OpenTelemetryAttributeSet + OpenTelemetryAttributeSet* attributes + = static_cast(options.Attributes.get()); + newSpan = m_tracer->StartSpan(spanName, *attributes, spanOptions); + } + else + { + newSpan = m_tracer->StartSpan(spanName, spanOptions); + } + + return std::make_shared( + newSpan); + } + + OpenTelemetrySpan::~OpenTelemetrySpan() {} + + OpenTelemetrySpan::OpenTelemetrySpan( + opentelemetry::nostd::shared_ptr span) + : m_span(span) + { + } + + void OpenTelemetrySpan::End(Azure::Nullable endTime) + { + opentelemetry::trace::EndSpanOptions options; + if (endTime) + { + options.end_steady_time = opentelemetry::common::SteadyTimestamp( + std::chrono::steady_clock::time_point(endTime.Value().time_since_epoch())); + } + m_span->End(options); + } + + /** + * @brief Add the set of attributes provided to the current span. + */ + void OpenTelemetrySpan::AddAttributes(AttributeSet const& attributesToAdd) + { + // Note: We make a huge assumption here: That if you're calling into the OpenTelemetry + // version of Azure::Core::Tracing, the Attributes passed in will be an + // OpenTelemetryAttributeSet + OpenTelemetryAttributeSet const& attributes + = static_cast(attributesToAdd); + attributes.ForEachKeyValue( + [this]( + opentelemetry::nostd::string_view name, opentelemetry::common::AttributeValue value) { + m_span->SetAttribute(name, value); + return true; + }); + } + + /** + * Add an Event to the span. An event is identified by a name and an optional set of + * attributes associated with the event. + */ + void OpenTelemetrySpan::AddEvent( + std::string const& eventName, + AttributeSet const& eventAttributes) + { + OpenTelemetryAttributeSet const& attributes + = static_cast(eventAttributes); + + m_span->AddEvent(eventName, attributes); + } + + void OpenTelemetrySpan::AddEvent(std::string const& eventName) { m_span->AddEvent(eventName); } + + void OpenTelemetrySpan::AddEvent(std::exception const& ex) { m_span->AddEvent(ex.what()); } + + void OpenTelemetrySpan::SetStatus(SpanStatus const& status, std::string const& statusMessage) + { + opentelemetry::trace::StatusCode statusCode = opentelemetry::trace::StatusCode::kUnset; + if (status == SpanStatus::Error) + { + statusCode = opentelemetry::trace::StatusCode::kError; + } + else if (status == SpanStatus::Ok) + { + statusCode = opentelemetry::trace::StatusCode::kOk; + } + else if (status == SpanStatus::Unset) + { + statusCode = opentelemetry::trace::StatusCode::kUnset; + } + else + { + throw std::runtime_error("Unknown status code: " + status.ToString()); + } + + m_span->SetStatus(statusCode, statusMessage); + } + + } // namespace _detail +}}}} // namespace Azure::Core::Tracing::OpenTelemetry diff --git a/sdk/core/azure-core-tracing-opentelemetry/src/private/package_version.hpp b/sdk/core/azure-core-tracing-opentelemetry/src/private/package_version.hpp new file mode 100644 index 0000000000..3912ae0efa --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/src/private/package_version.hpp @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Provides version information. + */ + +#pragma once + +#include + +#define AZURE_CORE_OPENTELEMETRY_VERSION_MAJOR 1 +#define AZURE_CORE_OPENTELEMETRY_VERSION_MINOR 0 +#define AZURE_CORE_OPENTELEMETRY_VERSION_PATCH 0 +#define AZURE_CORE_OPENTELEMETRY_VERSION_PRERELEASE "beta.1" + +#define AZURE_CORE_OPENTELEMETRY_VERSION_ITOA_HELPER(i) #i +#define AZURE_CORE_OPENTELEMETRY_VERSION_ITOA(i) AZURE_CORE_OPENTELEMETRY_VERSION_ITOA_HELPER(i) + +namespace Azure { namespace Core { namespace OpenTelemetry { namespace _detail { + /** + * @brief Provides version information. + * + */ + class PackageVersion final { + public: + /// Major numeric identifier. + static constexpr int32_t Major = AZURE_CORE_OPENTELEMETRY_VERSION_MAJOR; + + /// Minor numeric identifier. + static constexpr int32_t Minor = AZURE_CORE_OPENTELEMETRY_VERSION_MINOR; + + /// Patch numeric identifier. + static constexpr int32_t Patch = AZURE_CORE_OPENTELEMETRY_VERSION_PATCH; + + /// Indicates whether the SDK is in a pre-release state. + static constexpr bool IsPreRelease + = sizeof(AZURE_CORE_OPENTELEMETRY_VERSION_PRERELEASE) != sizeof(""); + + /** + * @brief The version in string format used for telemetry following the `semver.org` standard + * (https://semver.org). + */ + static constexpr const char* ToString() + { + return IsPreRelease + ? AZURE_CORE_OPENTELEMETRY_VERSION_ITOA(AZURE_CORE_OPENTELEMETRY_VERSION_MAJOR) "." AZURE_CORE_OPENTELEMETRY_VERSION_ITOA( + AZURE_CORE_OPENTELEMETRY_VERSION_MINOR) "." AZURE_CORE_OPENTELEMETRY_VERSION_ITOA(AZURE_CORE_OPENTELEMETRY_VERSION_PATCH) "-" AZURE_CORE_OPENTELEMETRY_VERSION_PRERELEASE + : AZURE_CORE_OPENTELEMETRY_VERSION_ITOA(AZURE_CORE_OPENTELEMETRY_VERSION_MAJOR) "." AZURE_CORE_OPENTELEMETRY_VERSION_ITOA( + AZURE_CORE_OPENTELEMETRY_VERSION_MINOR) "." AZURE_CORE_OPENTELEMETRY_VERSION_ITOA(AZURE_CORE_OPENTELEMETRY_VERSION_PATCH); + } + }; +}}}} // namespace Azure::Core::OpenTelemetry::_detail + +#undef AZURE_CORE_OPENTELEMETRY_VERSION_ITOA_HELPER +#undef AZURE_CORE_OPENTELEMETRY_VERSION_ITOA +#undef AZURE_CORE_OPENTELEMETRY_VERSION_MAJOR +#undef AZURE_CORE_OPENTELEMETRY_VERSION_MINOR +#undef AZURE_CORE_OPENTELEMETRY_VERSION_PATCH +#undef AZURE_CORE_OPENTELEMETRY_VERSION_PRERELEASE diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/CMakeLists.txt b/sdk/core/azure-core-tracing-opentelemetry/test/ut/CMakeLists.txt new file mode 100644 index 0000000000..4fa8c73f5b --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +cmake_minimum_required (VERSION 3.13) + +set(azure-core-tracing-opentelemetry-test) + +add_compile_definitions(AZURE_TEST_DATA_PATH="${CMAKE_BINARY_DIR}") + +add_compile_definitions(AZURE_TEST_RECORDING_DIR="${CMAKE_CURRENT_LIST_DIR}") + +project (azure-core-tracing-opentelemetry-test LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +include(GoogleTest) + +add_executable ( + azure-core-tracing-opentelemetry-test + azure_core_otel_test.cpp + azure_core_test.cpp service_support_test.cpp) + +if (MSVC) + # Disable warnings: + # - C26495: Variable + # - 'testing::internal::Mutex::critical_section_' + # - 'testing::internal::Mutex::critical_section_init_phase_' + # - 'testing::internal::Mutex::owner_thread_id_' + # - 'testing::internal::Mutex::type_' + # is uninitialized. Always initialize member variables (type.6). + # - C26812: The enum type + # - 'testing::internal::Mutex::StaticConstructorSelector' + # - 'testing::TestPartResult::Type' + # is unscoped. Prefer 'enum class' over 'enum' (Enum.3) + # - C6326: Google comparisons + target_compile_options(azure-core-test PUBLIC /wd26495 /wd26812 /wd6326 /wd28204 /wd28020 /wd6330 /wd4389) +endif() + +# Adding private headers from CORE to the tests so we can test the private APIs with no relative paths include. +target_include_directories (azure-core-tracing-opentelemetry-test PRIVATE $) + +target_link_libraries(azure-core-tracing-opentelemetry-test PRIVATE azure-core-tracing-opentelemetry + azure-core + azure-identity + opentelemetry-cpp::ostream_span_exporter + opentelemetry-cpp::in_memory_span_exporter + opentelemetry-cpp::sdk + azure-core-test-fw + gtest_main) + +create_per_service_target_build(core azure-core-tracing-opentelemetry-test) +create_map_file(azure-core-tracing-opentelemetry-test azure-core-tracing-opentelemetry-test.map) + +# gtest_discover_tests will scan the test from azure-core-opentelemetry-test and call add_test +# for each test to ctest. This enables `ctest -r` to run specific tests directly. +gtest_discover_tests(azure-core-tracing-opentelemetry-test + TEST_PREFIX azure-core-opentelemetry. + NO_PRETTY_TYPES + NO_PRETTY_VALUES) diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_otel_test.cpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_otel_test.cpp new file mode 100644 index 0000000000..4d1735581b --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_otel_test.cpp @@ -0,0 +1,692 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#define USE_MEMORY_EXPORTER 1 +#include "azure/core/tracing/opentelemetry/opentelemetry.hpp" +#include + +#if defined(_MSC_VER) +// The OpenTelemetry headers generate a couple of warnings on MSVC in the OTel 1.2 package, suppress +// the warnings across the includes. +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4244) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include +#include + +class OpenTelemetryTests : public Azure::Core::Test::TestBase { +private: + class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler { + void Handle( + opentelemetry::sdk::common::internal_log::LogLevel, + const char* file, + int line, + const char* msg, + const opentelemetry::sdk::common::AttributeMap& attributes) noexcept override + { + GTEST_LOG_(INFO) << "File: " << std::string(file) << " (" << line << "): " << std::string(msg) + << std::endl; + if (!attributes.empty()) + { + for (auto& attribute : attributes) + { + GTEST_LOG_(INFO) << "Attribute " << attribute.first << ": "; + switch (attribute.second.index()) + { + case opentelemetry::sdk::common::kTypeBool: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeInt: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeUInt: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeInt64: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeDouble: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + + case opentelemetry::sdk::common::kTypeString: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + + case opentelemetry::sdk::common::kTypeSpanBool: + case opentelemetry::sdk::common::kTypeSpanInt: + case opentelemetry::sdk::common::kTypeSpanUInt: + case opentelemetry::sdk::common::kTypeSpanInt64: + case opentelemetry::sdk::common::kTypeSpanDouble: + case opentelemetry::sdk::common::kTypeSpanString: + case opentelemetry::sdk::common::kTypeUInt64: + case opentelemetry::sdk::common::kTypeSpanUInt64: + case opentelemetry::sdk::common::kTypeSpanByte: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + GTEST_LOG_(INFO) << "SPAN"; + break; + } + GTEST_LOG_(INFO) << std::endl; + } + } + } + }; + +protected: + std::shared_ptr m_spanData; + + opentelemetry::nostd::shared_ptr + CreateOpenTelemetryProvider() + { +#if USE_MEMORY_EXPORTER + auto exporter = std::make_unique(); + m_spanData = exporter->GetData(); +#else + // logging exporter + auto exporter = std::make_unique(); +#endif + + // simple processor + auto simple_processor = std::unique_ptr( + new opentelemetry::sdk::trace::SimpleSpanProcessor(std::move(exporter))); + + auto always_on_sampler = std::unique_ptr( + new opentelemetry::sdk::trace::AlwaysOnSampler); + + auto resource_attributes = opentelemetry::sdk::resource::ResourceAttributes{ + {"service.name", "telemetryTest"}, {"service.instance.id", "instance-1"}}; + auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); + // Create using SDK configurations as parameter + return opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::trace::TracerProvider( + std::move(simple_processor), resource, std::move(always_on_sampler))); + } + + // Create + virtual void SetUp() override + { + Azure::Core::Test::TestBase::SetUpTestBase(AZURE_TEST_RECORDING_DIR); + + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler( + opentelemetry::nostd::shared_ptr( + new CustomLogHandler())); + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel( + opentelemetry::sdk::common::internal_log::LogLevel::Debug); + } + + virtual void TearDown() override + { + // Make sure you call the base classes TearDown method to ensure recordings are made. + TestBase::TearDown(); + } +}; + +TEST_F(OpenTelemetryTests, Basic) +{ + // Simple create an OTel telemetry provider as a static member variable. + { + Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider provider; + auto tracer = provider.CreateTracer("TracerName", "1.0"); + EXPECT_TRUE(tracer); + } + + // Create a shared provider using the tracing abstract classes. + { + std::shared_ptr provider + = std::make_shared(); + auto tracer = provider->CreateTracer("TracerName", "1.0"); + EXPECT_TRUE(tracer); + } + + // Create a provider using the OpenTelemetry default provider (this will be a "noop" provider). + { + auto rawTracer(opentelemetry::trace::Provider::GetTracerProvider()); + + auto traceProvider + = std::make_shared(rawTracer); + + auto tracer = traceProvider->CreateTracer("TracerName"); + EXPECT_TRUE(tracer); + } + + // Create a provider using the OpenTelemetry reference provider (this will be a working provider + // using the ostream logger). + { + auto otelProvider(CreateOpenTelemetryProvider()); + auto traceProvider + = std::make_shared( + otelProvider); + + auto tracer = traceProvider->CreateTracer("TracerName"); + } +} + +TEST_F(OpenTelemetryTests, CreateSpanSimple) +{ + // Simple create an OTel telemetry provider as a static member variable. + { + Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider provider; + auto tracer = provider.CreateTracer("TracerName", "1.0"); + EXPECT_TRUE(tracer); + auto span = tracer->CreateSpan("My Span"); + EXPECT_TRUE(span); + + span->End(); + } + + // Create a provider using the OpenTelemetry reference provider (this will be a working provider + // using the ostream logger). + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + { + auto span = tracer->CreateSpan("My Span2"); + EXPECT_TRUE(span); + + span->End(); + } + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + // Make sure that the span we collected looks right. + EXPECT_EQ("My Span2", spans[0]->GetName()); + EXPECT_EQ(opentelemetry::trace::StatusCode::kUnset, spans[0]->GetStatus()); + auto spanContext(spans[0]->GetSpanContext()); + EXPECT_TRUE(spanContext.IsValid()); + } +} + +TEST_F(OpenTelemetryTests, TestAttributeSet) +{ + { + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + } + + { + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + // Add a C style string. + attributeSet.AddAttribute("String", "StringValue"); + + attributeSet.ForEachKeyValue( + [](opentelemetry::nostd::string_view name, opentelemetry::common::AttributeValue value) { + EXPECT_EQ(name, "String"); + EXPECT_EQ(0, strcmp("StringValue", opentelemetry::nostd::get(value))); + return true; + }); + } + + { + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + attributeSet.AddAttribute("boolTrue", true); + attributeSet.AddAttribute("boolFalse", false); + + attributeSet.ForEachKeyValue( + [](opentelemetry::nostd::string_view name, opentelemetry::common::AttributeValue value) { + if (name == "boolTrue") + { + EXPECT_TRUE(opentelemetry::nostd::get(value)); + } + else if (name == "boolFalse") + { + EXPECT_FALSE(opentelemetry::nostd::get(value)); + } + else + { + EXPECT_TRUE(false); + } + return true; + }); + } + { + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + attributeSet.AddAttribute("int1", 1); + attributeSet.AddAttribute("pi", 3.1415926); + attributeSet.AddAttribute("int64", static_cast(151031ll)); + attributeSet.AddAttribute("uint64", static_cast(1ull)); + attributeSet.AddAttribute("charstring", "char * string."); + // Note that the attribute set doesn't take ownership of the input value, so we need to ensure + // the lifetime of any std::string values put into the set. + std::string stringValue("std::string."); + attributeSet.AddAttribute("stdstring", stringValue); + + attributeSet.ForEachKeyValue([](opentelemetry::nostd::string_view name, + opentelemetry::common::AttributeValue value) { + if (name == "int1") + { + EXPECT_EQ(1, opentelemetry::nostd::get(value)); + } + else if (name == "pi") + { + EXPECT_EQ(3.1415926, opentelemetry::nostd::get(value)); + } + else if (name == "int64") + { + EXPECT_EQ(151031, opentelemetry::nostd::get(value)); + } + else if (name == "uint64") + { + EXPECT_EQ(1, opentelemetry::nostd::get(value)); + } + else if (name == "charstring") + { + const char* cstrVal(opentelemetry::nostd::get(value)); + EXPECT_EQ(0, strcmp(cstrVal, "char * string.")); + } + else if (name == "stdstring") + { + EXPECT_EQ( + "std::string.", opentelemetry::nostd::get(value)); + } + else + { + EXPECT_TRUE(false); + } + return true; + }); + } +} + +TEST_F(OpenTelemetryTests, CreateSpanWithOptions) +{ + // Simple create an OTel telemetry provider as a static member variable. + { + Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider provider; + auto tracer = provider.CreateTracer("TracerName", "1.0"); + EXPECT_TRUE(tracer); + Azure::Core::Tracing::_internal::CreateSpanOptions options; + auto span = tracer->CreateSpan("My Span", options); + EXPECT_TRUE(span); + + span->End(); + } + + // Create a provider using the OpenTelemetry reference provider (this will be a working provider + // using the ostream logger). + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Client; + auto span = tracer->CreateSpan("Client Span", options); + EXPECT_TRUE(span); + + span->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Consumer; + auto span = tracer->CreateSpan("Consumer Span", options); + EXPECT_TRUE(span); + + span->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Internal; + auto span = tracer->CreateSpan("Internal Span", options); + EXPECT_TRUE(span); + + span->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Producer; + auto span = tracer->CreateSpan("Producer Span", options); + EXPECT_TRUE(span); + + span->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Server; + auto span = tracer->CreateSpan("Server Span", options); + EXPECT_TRUE(span); + + span->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = Azure::Core::Tracing::_internal::SpanKind("Bogus"); + EXPECT_THROW(tracer->CreateSpan("Bogus Span", options), std::runtime_error); + } + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(5ul, spans.size()); + // Make sure that the span we collected looks right. + EXPECT_EQ("Client Span", spans[0]->GetName()); + EXPECT_EQ(opentelemetry::trace::SpanKind::kClient, spans[0]->GetSpanKind()); + EXPECT_EQ("Consumer Span", spans[1]->GetName()); + EXPECT_EQ(opentelemetry::trace::SpanKind::kConsumer, spans[1]->GetSpanKind()); + EXPECT_EQ("Internal Span", spans[2]->GetName()); + EXPECT_EQ(opentelemetry::trace::SpanKind::kInternal, spans[2]->GetSpanKind()); + EXPECT_EQ("Producer Span", spans[3]->GetName()); + EXPECT_EQ(opentelemetry::trace::SpanKind::kProducer, spans[3]->GetSpanKind()); + EXPECT_EQ("Server Span", spans[4]->GetName()); + EXPECT_EQ(opentelemetry::trace::SpanKind::kServer, spans[4]->GetSpanKind()); + } + + { + // Create a provider using the OpenTelemetry reference provider (this will be a working provider + // using the ostream logger). + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + { + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Attributes = std::make_unique< + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet>(); + options.Attributes->AddAttribute("SimpleStringAttribute", "Simple String"); + options.Kind = Azure::Core::Tracing::_internal::SpanKind::Client; + auto span = tracer->CreateSpan("Client Span", options); + EXPECT_TRUE(span); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(static_cast(1), spans.size()); + // Make sure that the span we collected looks right. + EXPECT_EQ("Client Span", spans[0]->GetName()); + EXPECT_EQ(static_cast(1), spans[0]->GetAttributes().size()); + EXPECT_NE( + spans[0]->GetAttributes().end(), + spans[0]->GetAttributes().find("SimpleStringAttribute")); + EXPECT_EQ( + "Simple String", + opentelemetry::nostd::get( + spans[0]->GetAttributes().at("SimpleStringAttribute"))); + } + } + } +} + +TEST_F(OpenTelemetryTests, NestSpans) +{ + + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("SpanOuter"); + EXPECT_TRUE(span); + { + Azure::Core::Tracing::_internal::CreateSpanOptions so; + so.ParentSpan = span; + auto span2 = tracer->CreateSpan("SpanInner", so); + so.ParentSpan = span2; + auto span3 = tracer->CreateSpan("SpanInner2", so); + // Span 3's parent is SpanOuter. + so.ParentSpan = span; + auto span4 = tracer->CreateSpan("SpanInner4", so); + span2->End(); + + span->End(); + span4->End(); + span3->End(); + } + { + Azure::Core::Tracing::_internal::CreateSpanOptions so; + so.ParentSpan = span; + auto span5 = tracer->CreateSpan("SequentialInner", so); + auto span6 = tracer->CreateSpan("SequentialInner2", so); + span5->End(); + span6->End(); + } + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(6ul, spans.size()); + // Make sure that the span we collected looks right. + // The spans are ordered in the order they called "End", since a span that hasn't ended cannot + // be recorded. + EXPECT_EQ("SpanInner", spans[0]->GetName()); + EXPECT_EQ("SpanOuter", spans[1]->GetName()); + EXPECT_EQ("SpanInner4", spans[2]->GetName()); + EXPECT_EQ("SpanInner2", spans[3]->GetName()); + EXPECT_EQ("SequentialInner", spans[4]->GetName()); + EXPECT_EQ("SequentialInner2", spans[5]->GetName()); + EXPECT_FALSE(spans[1]->GetParentSpanId().IsValid()); // Span 1 should be a root span. + EXPECT_TRUE(spans[0]->GetParentSpanId().IsValid()); // Span 0 should not be a root span. + EXPECT_TRUE(spans[2]->GetParentSpanId().IsValid()); // Span 2 should not be a root span. + EXPECT_TRUE(spans[3]->GetParentSpanId().IsValid()); // Span 3 should not be a root span. + EXPECT_TRUE(spans[4]->GetParentSpanId().IsValid()); // Span 4 should not be a root span. + EXPECT_TRUE(spans[5]->GetParentSpanId().IsValid()); // Span 5 should not be a root span. + + // SpanInner should have SpanOuter as a parent. + EXPECT_EQ(spans[0]->GetParentSpanId(), spans[1]->GetSpanId()); + + // SpanInner2 should have SpanOuter as a parent. + EXPECT_EQ(spans[3]->GetParentSpanId(), spans[0]->GetSpanId()); + + // SpanInner4 should have SpanInner2 as a parent. + EXPECT_EQ(spans[2]->GetParentSpanId(), spans[1]->GetSpanId()); + + // SequentialInner should have SpanOuter as a parent. + EXPECT_EQ(spans[4]->GetParentSpanId(), spans[1]->GetSpanId()); + + // SequentialInner2 should have SpanOuter as a parent. + EXPECT_EQ(spans[5]->GetParentSpanId(), spans[1]->GetSpanId()); + } +} + +TEST_F(OpenTelemetryTests, SetStatus) +{ + + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("StatusSpan"); + EXPECT_TRUE(span); + + span->SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Error); + span->SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Ok); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + EXPECT_EQ(opentelemetry::trace::StatusCode::kOk, spans[0]->GetStatus()); + } + + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("StatusSpan"); + EXPECT_TRUE(span); + + span->SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Error, "Something went wrong."); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + EXPECT_EQ(opentelemetry::trace::StatusCode::kError, spans[0]->GetStatus()); + EXPECT_EQ("Something went wrong.", spans[0]->GetDescription()); + } + + // Set to Unset. + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("StatusSpan"); + EXPECT_TRUE(span); + + span->SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Unset); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + EXPECT_EQ(opentelemetry::trace::StatusCode::kUnset, spans[0]->GetStatus()); + } + + // Not set. + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("StatusSpan"); + EXPECT_TRUE(span); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + EXPECT_EQ(opentelemetry::trace::StatusCode::kUnset, spans[0]->GetStatus()); + } + + // Invalid status. + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("StatusSpan"); + EXPECT_TRUE(span); + + EXPECT_THROW( + span->SetStatus(Azure::Core::Tracing::_internal::SpanStatus("Bogus")), std::runtime_error); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(0ul, spans.size()); + } +} + +TEST_F(OpenTelemetryTests, AddSpanAttributes) +{ + + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("AttributeSpan"); + EXPECT_TRUE(span); + + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + attributeSet.AddAttribute("int1", 1); + attributeSet.AddAttribute("pi", 3.1415926); + attributeSet.AddAttribute("int64", static_cast(151031ll)); + attributeSet.AddAttribute("uint64", static_cast(1ull)); + attributeSet.AddAttribute("charstring", "char * string."); + // Note that the attribute set doesn't take ownership of the input value, so we need to ensure + // the lifetime of any std::string values put into the set. + std::string stringValue("std::string."); + attributeSet.AddAttribute("stdstring", stringValue); + span->AddAttributes(attributeSet); + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + // Make sure that the span we collected looks right. + EXPECT_EQ("AttributeSpan", spans[0]->GetName()); + EXPECT_EQ(6ul, spans[0]->GetAttributes().size()); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("int1")); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("pi")); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("int64")); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("uint64")); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("charstring")); + EXPECT_NE(spans[0]->GetAttributes().end(), spans[0]->GetAttributes().find("stdstring")); + } +} + +TEST_F(OpenTelemetryTests, AddSpanEvents) +{ + { + std::shared_ptr traceProvider + = std::make_shared( + CreateOpenTelemetryProvider()); + + auto tracer = traceProvider->CreateTracer("TracerName"); + auto span = tracer->CreateSpan("SpanWithEvents"); + EXPECT_TRUE(span); + + span->AddEvent("String Event"); + span->AddEvent(std::runtime_error("Exception message")); + + { + Azure::Core::Tracing::OpenTelemetry::_detail::OpenTelemetryAttributeSet attributeSet; + attributeSet.AddAttribute("int1", 1); + attributeSet.AddAttribute("pi", 3.1415926); + attributeSet.AddAttribute("int64", static_cast(151031ll)); + attributeSet.AddAttribute("uint64", static_cast(1ull)); + attributeSet.AddAttribute("charstring", "char * string."); + // Note that the attribute set doesn't take ownership of the input value, so we need to ensure + // the lifetime of any std::string values put into the set. + std::string stringValue("std::string."); + attributeSet.AddAttribute("stdstring", stringValue); + span->AddEvent("Event With Attributes", attributeSet); + + span->End(); + + // Return the collected spans. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + EXPECT_EQ(3UL, spans[0]->GetEvents().size()); + + EXPECT_EQ("String Event", spans[0]->GetEvents()[0].GetName()); + EXPECT_EQ("Exception message", spans[0]->GetEvents()[1].GetName()); + EXPECT_EQ("Event With Attributes", spans[0]->GetEvents()[2].GetName()); + + const auto& attributes = spans[0]->GetEvents()[2].GetAttributes(); + + // Make sure that the span we collected looks right. + EXPECT_EQ(6ul, attributes.size()); + EXPECT_NE(attributes.end(), attributes.find("int1")); + EXPECT_NE(attributes.end(), attributes.find("pi")); + EXPECT_NE(attributes.end(), attributes.find("int64")); + EXPECT_NE(attributes.end(), attributes.find("uint64")); + EXPECT_NE(attributes.end(), attributes.find("charstring")); + EXPECT_NE(attributes.end(), attributes.find("stdstring")); + } + } +} diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp new file mode 100644 index 0000000000..b847ddb3c1 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/azure_core_test.cpp @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include + +int main(int argc, char** argv) +{ +#if defined(AZ_PLATFORM_POSIX) + // OpenSSL signals SIGPIPE when trying to clean an HTTPS closed connection. + // End users need to decide if SIGPIPE should be ignored or not. + signal(SIGPIPE, SIG_IGN); +#endif + + testing::InitGoogleTest(&argc, argv); + auto r = RUN_ALL_TESTS(); + + return r; +} diff --git a/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp b/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp new file mode 100644 index 0000000000..c48f4abfda --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/test/ut/service_support_test.cpp @@ -0,0 +1,601 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#define USE_MEMORY_EXPORTER 1 +#include "azure/core/internal/tracing/service_tracing.hpp" +#include "azure/core/tracing/opentelemetry/opentelemetry.hpp" +#include +#include + +#if defined(_MSC_VER) +// The OpenTelemetry headers generate a couple of warnings on MSVC in the OTel 1.2 package, suppress +// the warnings across the includes. +#pragma warning(push) +#pragma warning(disable : 4100) +#pragma warning(disable : 4244) +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include +#include + +class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler { + void Handle( + opentelemetry::sdk::common::internal_log::LogLevel, + const char* file, + int line, + const char* msg, + const opentelemetry::sdk::common::AttributeMap& attributes) noexcept override + { + GTEST_LOG_(INFO) << "File: " << std::string(file) << " (" << line << "): " << std::string(msg) + << std::endl; + if (!attributes.empty()) + { + for (auto& attribute : attributes) + { + GTEST_LOG_(INFO) << "Attribute " << attribute.first << ": "; + switch (attribute.second.index()) + { + case opentelemetry::sdk::common::kTypeBool: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeInt: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeUInt: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeInt64: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + case opentelemetry::sdk::common::kTypeDouble: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + + case opentelemetry::sdk::common::kTypeString: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + break; + + case opentelemetry::sdk::common::kTypeSpanBool: + case opentelemetry::sdk::common::kTypeSpanInt: + case opentelemetry::sdk::common::kTypeSpanUInt: + case opentelemetry::sdk::common::kTypeSpanInt64: + case opentelemetry::sdk::common::kTypeSpanDouble: + case opentelemetry::sdk::common::kTypeSpanString: + case opentelemetry::sdk::common::kTypeUInt64: + case opentelemetry::sdk::common::kTypeSpanUInt64: + case opentelemetry::sdk::common::kTypeSpanByte: + GTEST_LOG_(INFO) << opentelemetry::nostd::get(attribute.second); + GTEST_LOG_(INFO) << "SPAN"; + break; + } + GTEST_LOG_(INFO) << std::endl; + } + } + } +}; + +class OpenTelemetryServiceTests : public Azure::Core::Test::TestBase { +private: +protected: + std::shared_ptr m_spanData; + + opentelemetry::nostd::shared_ptr + CreateOpenTelemetryProvider() + { +#if USE_MEMORY_EXPORTER + auto exporter = std::make_unique(); + m_spanData = exporter->GetData(); +#else + // logging exporter + auto exporter = std::make_unique(); +#endif + + // simple processor + auto simple_processor = std::unique_ptr( + new opentelemetry::sdk::trace::SimpleSpanProcessor(std::move(exporter))); + + auto always_on_sampler = std::unique_ptr( + new opentelemetry::sdk::trace::AlwaysOnSampler); + + auto resource_attributes = opentelemetry::sdk::resource::ResourceAttributes{ + {"service.name", "telemetryTest"}, {"service.instance.id", "instance-1"}}; + auto resource = opentelemetry::sdk::resource::Resource::Create(resource_attributes); + // Create using SDK configurations as parameter + return opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::trace::TracerProvider( + std::move(simple_processor), resource, std::move(always_on_sampler))); + } + + // Create + virtual void SetUp() override + { + Azure::Core::Test::TestBase::SetUpTestBase(AZURE_TEST_RECORDING_DIR); + + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogHandler( + opentelemetry::nostd::shared_ptr( + new CustomLogHandler())); + opentelemetry::sdk::common::internal_log::GlobalLogHandler::SetLogLevel( + opentelemetry::sdk::common::internal_log::LogLevel::Debug); + } + + virtual void TearDown() override + { + // Make sure you call the base classes TearDown method to ensure recordings are made. + TestBase::TearDown(); + } + + bool VerifySpan( + std::unique_ptr const& span, + std::string const& expectedSpanContentsJson) + { + Azure::Core::Json::_internal::json expectedSpanContents( + Azure::Core::Json::_internal::json::parse(expectedSpanContentsJson)); + EXPECT_EQ(expectedSpanContents["name"].get(), span->GetName()); + if (expectedSpanContents.contains("statusCode")) + { + std::string expectedStatus = expectedSpanContents["statusCode"].get(); + switch (span->GetStatus()) + { + case opentelemetry::trace::StatusCode::kOk: + EXPECT_EQ(expectedStatus, "ok"); + break; + case opentelemetry::trace::StatusCode::kError: + EXPECT_EQ(expectedStatus, "error"); + break; + case opentelemetry::trace::StatusCode::kUnset: + EXPECT_EQ(expectedStatus, "unset"); + break; + default: + throw std::runtime_error("Unknown span status"); + } + } + if (expectedSpanContents.contains("kind")) + { + std::string expectedKind = expectedSpanContents["kind"].get(); + switch (span->GetSpanKind()) + { + case opentelemetry::trace::SpanKind::kClient: + EXPECT_EQ(expectedKind, "internal"); + break; + case opentelemetry::trace::SpanKind::kConsumer: + EXPECT_EQ(expectedKind, "consumer"); + break; + case opentelemetry::trace::SpanKind::kInternal: + EXPECT_EQ(expectedKind, "internal"); + break; + case opentelemetry::trace::SpanKind::kProducer: + EXPECT_EQ(expectedKind, "producer"); + break; + case opentelemetry::trace::SpanKind::kServer: + EXPECT_EQ(expectedKind, "server"); + break; + default: + throw std::runtime_error("Unknown span kind"); + } + } + if (expectedSpanContents.contains("attributes")) + { + auto& expectedAttributes = expectedSpanContents["attributes"]; + EXPECT_TRUE(expectedAttributes.is_object()); + auto attributes(span->GetAttributes()); + + EXPECT_EQ(expectedAttributes.size(), attributes.size()); + + for (const auto& foundAttribute : attributes) + { + EXPECT_TRUE(expectedAttributes.contains(foundAttribute.first)); + switch (foundAttribute.second.index()) + { + case opentelemetry::common::kTypeBool: { + + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_boolean()); + auto actualVal = opentelemetry::nostd::get(foundAttribute.second); + EXPECT_EQ(expectedAttributes[foundAttribute.first].get(), actualVal); + break; + } + case opentelemetry::common::kTypeCString: + case opentelemetry::common::kTypeString: { + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_string()); + const auto& actualVal = opentelemetry::nostd::get(foundAttribute.second); + EXPECT_EQ(expectedAttributes[foundAttribute.first].get(), actualVal); + break; + } + case opentelemetry::common::kTypeDouble: { + + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_number()); + auto actualVal = opentelemetry::nostd::get(foundAttribute.second); + EXPECT_EQ(expectedAttributes[foundAttribute.first].get(), actualVal); + break; + } + + case opentelemetry::common::kTypeInt: + case opentelemetry::common::kTypeInt64: + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_number_integer()); + break; + case opentelemetry::common::kTypeSpanBool: + case opentelemetry::common::kTypeSpanByte: + case opentelemetry::common::kTypeSpanDouble: + case opentelemetry::common::kTypeSpanInt: + case opentelemetry::common::kTypeSpanInt64: + case opentelemetry::common::kTypeSpanString: + case opentelemetry::common::kTypeSpanUInt: + case opentelemetry::common::kTypeSpanUInt64: + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_array()); + throw std::runtime_error("Unsupported attribute kind"); + break; + + case opentelemetry::common::kTypeUInt: + case opentelemetry::common::kTypeUInt64: + EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_number_unsigned()); + break; + default: + throw std::runtime_error("Unknown attribute kind"); + break; + } + } + + // const auto& namespaceVal = opentelemetry::nostd::get(azNamespace); + } + if (expectedSpanContents.contains("library")) + { + EXPECT_EQ( + expectedSpanContents["library"]["name"].get(), + span->GetInstrumentationLibrary().GetName()); + EXPECT_EQ( + expectedSpanContents["library"]["version"].get(), + span->GetInstrumentationLibrary().GetVersion()); + } + return true; + } +}; + +TEST_F(OpenTelemetryServiceTests, SimplestTest) +{ + { + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace; + } + { + Azure::Core::_internal::ClientOptions clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + } + + { + Azure::Core::_internal::ClientOptions clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + + auto contextAndSpan = serviceTrace.CreateSpan("My API", {}); + EXPECT_FALSE(contextAndSpan.first.IsCancelled()); + } +} + +TEST_F(OpenTelemetryServiceTests, CreateWithExplicitProvider) +{ + // Create a serviceTrace, set it and retrieve it via a Context object. This verifies that we can + // round-trip telemetry providers through a Context (which allows us to hook this up to the + // ApplicationContext later). + // + { + auto tracerProvider(CreateOpenTelemetryProvider()); + auto provider(std::make_shared( + tracerProvider)); + + Azure::Core::Context rootContext; + rootContext.SetTracerProvider(provider); + EXPECT_EQ(provider, rootContext.GetTracerProvider()); + } + + { + auto tracerProvider(CreateOpenTelemetryProvider()); + auto provider(std::make_shared( + tracerProvider)); + + // Create a serviceTrace and span using a provider specified in the ClientOptions. + { + Azure::Core::_internal::ClientOptions clientOptions; + clientOptions.Telemetry.TracingProvider = provider; + clientOptions.Telemetry.ApplicationId = "MyApplication"; + + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service", "1.0beta-2"); + + Azure::Core::Context clientContext; + auto contextAndSpan = serviceTrace.CreateSpan("My API", clientContext); + EXPECT_FALSE(contextAndSpan.first.IsCancelled()); + } + // Now let's verify what was logged via OpenTelemetry. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + VerifySpan(spans[0], R"( +{ + "name": "My API", + "attributes": { + "az.namespace": "my-service" + }, + "library": { + "name": "my-service", + "version": "1.0beta-2" + } +})"); + } +} + +TEST_F(OpenTelemetryServiceTests, CreateWithImplicitProvider) +{ + { + auto tracerProvider(CreateOpenTelemetryProvider()); + auto provider(std::make_shared( + tracerProvider)); + + Azure::Core::Context::ApplicationContext.SetTracerProvider(provider); + + { + Azure::Core::_internal::ClientOptions clientOptions; + clientOptions.Telemetry.ApplicationId = "MyApplication"; + + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service", "1.0beta-2"); + + Azure::Core::Context clientContext; + auto contextAndSpan = serviceTrace.CreateSpan("My API", clientContext); + EXPECT_FALSE(contextAndSpan.first.IsCancelled()); + } + + // Now let's verify what was logged via OpenTelemetry. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(1ul, spans.size()); + + VerifySpan(spans[0], R"( +{ + "name": "My API", + "attributes": { + "az.namespace": "my-service" + }, + "library": { + "name": "my-service", + "version": "1.0beta-2" + } +})"); + } + + // Clear the global tracer provider set earlier in the test. + Azure::Core::Context::ApplicationContext.SetTracerProvider(nullptr); +} + +TEST_F(OpenTelemetryServiceTests, NestSpans) +{ + { + auto tracerProvider(CreateOpenTelemetryProvider()); + auto provider(std::make_shared( + tracerProvider)); + + Azure::Core::Context::ApplicationContext.SetTracerProvider(provider); + + { + Azure::Core::_internal::ClientOptions clientOptions; + clientOptions.Telemetry.ApplicationId = "MyApplication"; + + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service", "1.0beta-2"); + + Azure::Core::Context parentContext; + auto contextAndSpan = serviceTrace.CreateSpan("My API", parentContext); + EXPECT_FALSE(contextAndSpan.first.IsCancelled()); + parentContext = contextAndSpan.first; + + { + auto innerContextAndSpan = serviceTrace.CreateSpan("Nested API", parentContext); + EXPECT_FALSE(innerContextAndSpan.first.IsCancelled()); + } + } + // Now let's verify what was logged via OpenTelemetry. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(2ul, spans.size()); + + // Because Nested API goes out of scope before My API, it will be logged first in the + // tracing spans. + { + EXPECT_EQ("Nested API", spans[0]->GetName()); + EXPECT_TRUE(spans[0]->GetParentSpanId().IsValid()); + // The nested span should have the outer span as a parent. + EXPECT_EQ(spans[1]->GetSpanId(), spans[0]->GetParentSpanId()); + + const auto& attributes = spans[0]->GetAttributes(); + EXPECT_EQ(1ul, attributes.size()); + EXPECT_EQ( + "my-service", opentelemetry::nostd::get(attributes.at("az.namespace"))); + } + { + EXPECT_EQ("My API", spans[1]->GetName()); + EXPECT_FALSE(spans[1]->GetParentSpanId().IsValid()); + + const auto& attributes = spans[1]->GetAttributes(); + EXPECT_EQ(1ul, attributes.size()); + EXPECT_EQ( + "my-service", opentelemetry::nostd::get(attributes.at("az.namespace"))); + } + + EXPECT_EQ("my-service", spans[0]->GetInstrumentationLibrary().GetName()); + EXPECT_EQ("my-service", spans[1]->GetInstrumentationLibrary().GetName()); + EXPECT_EQ("1.0beta-2", spans[0]->GetInstrumentationLibrary().GetVersion()); + EXPECT_EQ("1.0beta-2", spans[1]->GetInstrumentationLibrary().GetVersion()); + } +} + +// Create a serviceTrace and span using a provider specified in the ClientOptions. +class ServiceClientOptions : public Azure::Core::_internal::ClientOptions { +public: + explicit ServiceClientOptions() : ClientOptions() {} +}; + +class ServiceClient { +private: + ServiceClientOptions m_clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory m_serviceTrace; + +public: + explicit ServiceClient(ServiceClientOptions const& clientOptions = ServiceClientOptions{}) + : m_serviceTrace(clientOptions, "Azure.Core.OpenTelemetry.Test.Service", "1.0.0.beta-2") + { + } + + Azure::Response GetConfigurationString( + std::string const& inputString, + Azure::Core::Context const& context = Azure::Core::Context{}) + { + auto contextAndSpan = m_serviceTrace.CreateSpan("GetConfigurationString", context); + + // + std::unique_ptr response + = SendHttpRequest(false, contextAndSpan.first); + + // Reflect that the operation was successful. + contextAndSpan.second.SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Ok); + Azure::Response rv(inputString, std::move(response)); + return rv; + // When contextAndSpan.second goes out of scope, it ends the span, which will record it. + } + + std::unique_ptr ActuallySendHttpRequest( + Azure::Core::Context const& context) + { + auto contextAndSpan + = Azure::Core::Tracing::_internal::DiagnosticTracingFactory::CreateSpanFromContext( + "HTTP GET#2", context); + + return std::make_unique( + 1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + } + + std::unique_ptr SendHttpRequest( + bool throwException, + Azure::Core::Context const& context) + { + if (throwException) + { + throw Azure::Core::RequestFailedException("it all goes wrong here."); + } + + auto contextAndSpan + = Azure::Core::Tracing::_internal::DiagnosticTracingFactory::CreateSpanFromContext( + "HTTP GET#1", context); + + std::unique_ptr response + = ActuallySendHttpRequest(contextAndSpan.first); + + return std::make_unique( + 1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + } + + Azure::Response ApiWhichThrows( + std::string const&, + Azure::Core::Context const& context = Azure::Core::Context{}) + { + auto contextAndSpan = m_serviceTrace.CreateSpan("ApiWhichThrows", context); + + try + { + auto rawResponse = SendHttpRequest(false, contextAndSpan.first); + return Azure::Response("", std::move(rawResponse)); + } + catch (std::exception const& ex) + { + // Register that the exception has happened and that the span is now in error. + contextAndSpan.second.AddEvent(ex); + contextAndSpan.second.SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Error); + throw; + } + + // When contextAndSpan.second goes out of scope, it ends the span, which will record it. + } +}; + +TEST_F(OpenTelemetryServiceTests, ServiceApiImplementation) +{ + { + auto tracerProvider(CreateOpenTelemetryProvider()); + auto provider(std::make_shared( + tracerProvider)); + + { + // Call a simple API and verify telemetry is generated. + { + ServiceClientOptions clientOptions; + clientOptions.Telemetry.TracingProvider = provider; + clientOptions.Telemetry.ApplicationId = "MyApplication"; + ServiceClient myServiceClient(clientOptions); + + myServiceClient.GetConfigurationString("Fred"); + } + // Now let's verify what was logged via OpenTelemetry. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(3ul, spans.size()); + + VerifySpan(spans[0], R"( +{ + "name": "HTTP GET#2", + "kind": "internal", + "statusCode": "unset", + "attributes": { + "az.namespace": "Azure.Core.OpenTelemetry.Test.Service" + }, + "library": { + "name": "Azure.Core.OpenTelemetry.Test.Service", + "version": "1.0.0.beta-2" + } +})"); + + VerifySpan(spans[1], R"( +{ + "name": "HTTP GET#1", + "kind": "internal", + "statusCode": "unset", + "attributes": { + "az.namespace": "Azure.Core.OpenTelemetry.Test.Service" + }, + "library": { + "name": "Azure.Core.OpenTelemetry.Test.Service", + "version": "1.0.0.beta-2" + } +})"); + + VerifySpan(spans[2], R"( +{ + "name": "GetConfigurationString", + "kind": "internal", + "statusCode": "ok", + "attributes": { + "az.namespace": "Azure.Core.OpenTelemetry.Test.Service" + }, + "library": { + "name": "Azure.Core.OpenTelemetry.Test.Service", + "version": "1.0.0.beta-2" + } +})"); + } + } + // Call into the fake service client ensuring that no telemetry is generated. + { + // Call a simple API and verify no telemetry is generated. + { + ServiceClient myServiceClient; + + myServiceClient.GetConfigurationString("George"); + } + // Now let's verify what was logged via OpenTelemetry. + auto spans = m_spanData->GetSpans(); + EXPECT_EQ(0ul, spans.size()); + } +} diff --git a/sdk/core/azure-core-tracing-opentelemetry/vcpkg.json b/sdk/core/azure-core-tracing-opentelemetry/vcpkg.json new file mode 100644 index 0000000000..d0c0d44412 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/vcpkg.json @@ -0,0 +1,18 @@ +{ + "name": "azure-core-tracing-opentelemetry-cpp", + "version-string": "1.0.0-beta.1", + "dependencies": [ + { + "name": "opentelemetry-cpp", + "platform": "!uwp" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} diff --git a/sdk/core/azure-core-tracing-opentelemetry/vcpkg/Config.cmake.in b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/Config.cmake.in new file mode 100644 index 0000000000..4837bd9dfb --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/Config.cmake.in @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Threads) + +find_dependency(opentelemetry-cpp@CURL_MIN_REQUIRED_VERSION@) + +include("${CMAKE_CURRENT_LIST_DIR}/azure-core-opentelemetry-cppTargets.cmake") + +check_required_components("azure-core-opentelemetry-cpp") diff --git a/sdk/core/azure-core-tracing-opentelemetry/vcpkg/portfile.cmake b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/portfile.cmake new file mode 100644 index 0000000000..71d34d177e --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/portfile.cmake @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO Azure/azure-sdk-for-cpp + REF azure-core_coretracing@AZ_LIBRARY_VERSION@ + SHA512 0 +) + +vcpkg_cmake_configure( + SOURCE_PATH ${SOURCE_PATH}/sdk/core/azure-core/ + OPTIONS + ${FEATURE_OPTIONS} + -DWARNINGS_AS_ERRORS=OFF +) + +vcpkg_cmake_install() +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +vcpkg_cmake_config_fixup() +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") +vcpkg_copy_pdbs() diff --git a/sdk/core/azure-core-tracing-opentelemetry/vcpkg/vcpkg.json b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/vcpkg.json new file mode 100644 index 0000000000..1db17176ac --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/vcpkg/vcpkg.json @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: MIT + +{ + "name": "azure-core-opentelemetry-cpp", + "version-semver": "@AZ_LIBRARY_VERSION@", + "description": [ + "Microsoft Azure Core OpenTelemetry SDK for C++", + "This library provides support for modern Azure SDK client libraries written in C++ to leverage OpenTelemetry APIs." + ], + "homepage": "https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/core/azure-core-opentelemetry", + "license": "MIT", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + { + "name": "opentelemetry-cpp" + } + ] +} diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index ea1d9d9db9..ac1c85cb31 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.7.0-beta.1 (Unreleased) ### Features Added +Added implementation for Distributed Tracing. ### Breaking Changes diff --git a/sdk/core/azure-core/CMakeLists.txt b/sdk/core/azure-core/CMakeLists.txt index 8a00fd083d..5bc4145465 100644 --- a/sdk/core/azure-core/CMakeLists.txt +++ b/sdk/core/azure-core/CMakeLists.txt @@ -77,6 +77,7 @@ set( inc/azure/core/internal/environment.hpp inc/azure/core/internal/extendable_enumeration.hpp inc/azure/core/internal/strings.hpp + inc/azure/core/internal/tracing/service_tracing.hpp inc/azure/core/io/body_stream.hpp inc/azure/core/azure_assert.hpp inc/azure/core/base64.hpp @@ -95,10 +96,10 @@ set( inc/azure/core/platform.hpp inc/azure/core/response.hpp inc/azure/core/rtti.hpp + inc/azure/core/tracing/tracing.hpp inc/azure/core/url.hpp inc/azure/core/uuid.hpp - inc/azure/core.hpp -) + inc/azure/core.hpp) set( AZURE_CORE_SOURCE @@ -132,7 +133,7 @@ set( src/operation_status.cpp src/strings.cpp src/uuid.cpp -) + src/tracing/tracing.cpp) add_library(azure-core ${AZURE_CORE_HEADER} ${AZURE_CORE_SOURCE}) diff --git a/sdk/core/azure-core/inc/azure/core.hpp b/sdk/core/azure-core/inc/azure/core.hpp index 6b7e1aa75e..b966270876 100644 --- a/sdk/core/azure-core/inc/azure/core.hpp +++ b/sdk/core/azure-core/inc/azure/core.hpp @@ -50,3 +50,6 @@ // azure/core/io #include "azure/core/io/body_stream.hpp" + +// azure/core/tracing +#include "azure/core/tracing/tracing.hpp" diff --git a/sdk/core/azure-core/inc/azure/core/context.hpp b/sdk/core/azure-core/inc/azure/core/context.hpp index a4ae92e631..67d55fddb7 100644 --- a/sdk/core/azure-core/inc/azure/core/context.hpp +++ b/sdk/core/azure-core/inc/azure/core/context.hpp @@ -12,7 +12,7 @@ #include "azure/core/datetime.hpp" #include "azure/core/dll_import_export.hpp" #include "azure/core/rtti.hpp" - +#include "azure/core/tracing/tracing.hpp" #include #include #include @@ -76,6 +76,7 @@ namespace Azure { namespace Core { { std::shared_ptr Parent; std::atomic Deadline; + std::shared_ptr TraceProvider; Context::Key Key; std::shared_ptr Value; #if defined(AZ_CORE_RTTI) @@ -248,6 +249,22 @@ namespace Azure { namespace Core { } } + /** + * @brief Returns the tracer provider for the current context. + */ + std::shared_ptr GetTracerProvider() + { + return m_contextSharedState->TraceProvider; + } + + /** + * @brief Sets the tracer provider for the current context. + */ + void SetTracerProvider(std::shared_ptr tracerProvider) + { + m_contextSharedState->TraceProvider = tracerProvider; + } + /** * @brief The application context (root). * diff --git a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp index abcc44850d..65c394d40b 100644 --- a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp @@ -14,6 +14,7 @@ #include "azure/core/dll_import_export.hpp" #include "azure/core/http/http.hpp" #include "azure/core/http/transport.hpp" +#include "azure/core/tracing/tracing.hpp" #include "azure/core/uuid.hpp" #include @@ -56,6 +57,13 @@ namespace Azure { namespace Core { namespace Http { namespace Policies { * */ std::string ApplicationId; + + /** + * @brief Specifies the default distributed tracing provider to use for this client. By default, + * this will be the tracing provider specified in the application context. + */ + std::shared_ptr TracingProvider{ + Context::ApplicationContext.GetTracerProvider()}; }; /** diff --git a/sdk/core/azure-core/inc/azure/core/internal/extendable_enumeration.hpp b/sdk/core/azure-core/inc/azure/core/internal/extendable_enumeration.hpp index 6bf7244a59..c84c71e78b 100644 --- a/sdk/core/azure-core/inc/azure/core/internal/extendable_enumeration.hpp +++ b/sdk/core/azure-core/inc/azure/core/internal/extendable_enumeration.hpp @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +#pragma once /** * @file diff --git a/sdk/core/azure-core/inc/azure/core/internal/tracing/service_tracing.hpp b/sdk/core/azure-core/inc/azure/core/internal/tracing/service_tracing.hpp new file mode 100644 index 0000000000..9b10b2a355 --- /dev/null +++ b/sdk/core/azure-core/inc/azure/core/internal/tracing/service_tracing.hpp @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/core/context.hpp" +#include "azure/core/internal/client_options.hpp" +#include "azure/core/tracing/tracing.hpp" + +#pragma once + +/** + * + * @brief Helper classes to enable service client distributed tracing implementations. + * + * @remark See #policy.hpp + */ +namespace Azure { namespace Core { namespace Tracing { namespace _internal { + + /** + * @brief RAII Helper class for Azure::Core::Tracing::Span objects. + * + * @details The ServiceSpan object is an RAII helper object used to manage Span objects. + * + * Before a Span is registered with OpenTelemetry, the span needs to have called the + * "Azure::Core::Tracing::Span::End" method. The ServiceSpan method wraps an + * Azure::Core::Tracing::Span object and ensures that the "End" method is called in the destructor + * for the span. + */ + class ServiceSpan final : public Span { + private: + std::shared_ptr m_span; + + friend class DiagnosticTracingFactory; + ServiceSpan() = default; + explicit ServiceSpan(std::shared_ptr span) : m_span(span) {} + + ServiceSpan(const ServiceSpan&) = delete; + ServiceSpan& operator=(ServiceSpan const&) = delete; + + ServiceSpan& operator=(ServiceSpan&&) noexcept = default; + + public: + ServiceSpan(ServiceSpan&& that) = default; + + ~ServiceSpan() + { + if (m_span) + { + m_span->End(); + } + } + + void End(Azure::Nullable = Azure::Nullable{}) override + { + if (m_span) + { + m_span->End(); + } + } + void SetStatus( + Azure::Core::Tracing::_internal::SpanStatus const& status, + std::string const& description = "") override + { + if (m_span) + { + m_span->SetStatus(status, description); + } + } + /** + * @brief Adds a set of attributes to the span. + * + * @param attributeToAdd Attributes to be added to the span. + */ + virtual void AddAttributes(AttributeSet const& attributeToAdd) override + { + if (m_span) + { + m_span->AddAttributes(attributeToAdd); + } + } + + /** + * @brief Adds an event to the span. + * + * Add an Event to the span. An event is identified by a name and an optional set of attributes + * associated with the event. + * + * @param eventName Name of the event to add. + * @param eventAttributes Attributes associated with the event. + */ + virtual void AddEvent(std::string const& eventName, AttributeSet const& eventAttributes) + override + { + if (m_span) + { + m_span->AddEvent(eventName, eventAttributes); + } + } + + /** + * @brief Adds an event to the span. + * + * Add an Event to the span. An event is identified by a name + * + * @param eventName Name of the event to add. + */ + virtual void AddEvent(std::string const& eventName) override + { + if (m_span) + { + m_span->AddEvent(eventName); + } + } + + /** + * @brief Records an exception occurring in the span. + * + * @param exception Exception which has occurred. + */ + virtual void AddEvent(std::exception const& exception) override + { + if (m_span) + { + m_span->AddEvent(exception); + } + } + }; + + /** + * @brief Helper class to enable distributed tracing for the service. + * + * @details Each service implementation SHOULD have a member variable which aids in managing + * the distributed tracing for the service. + */ + class DiagnosticTracingFactory final { + private: + std::string m_serviceName; + std::string m_serviceVersion; + std::shared_ptr m_serviceTracer; + + /** @brief The key used to retrieve the span and tracer associated with a context object. + * + * The value stored in the context with this key is a `std::pair, + * std::shared_ptr>`. + * + * A caller can use the Span and Tracer to create a new span associated with the current + * context span. + */ + static Azure::Core::Context::Key ContextSpanKey; + static Azure::Core::Context::Key TracingFactoryContextKey; + // using TracingContext = std::pair, std::shared_ptr>; + using TracingContext = std::shared_ptr; + + static DiagnosticTracingFactory* DiagnosticFactoryFromContext( + Azure::Core::Context const& context); + + static Azure::Nullable TracingContextFromContext( + Azure::Core::Context const& context); + + public: + DiagnosticTracingFactory( + Azure::Core::_internal::ClientOptions const& options, + std::string serviceName, + std::string serviceVersion) + : m_serviceName(serviceName), m_serviceVersion(serviceVersion), + m_serviceTracer( + options.Telemetry.TracingProvider + ? options.Telemetry.TracingProvider->CreateTracer(serviceName, serviceVersion) + : nullptr) + { + } + + DiagnosticTracingFactory() = default; + + /** @brief A ContextAndSpan provides an updated Context object and a new span object + * which can be used to add events and attributes to the span. + */ + using ContextAndSpan = std::pair; + + ContextAndSpan CreateSpan( + std::string const& spanName, + Azure::Core::Context const& clientContext); + + static ContextAndSpan CreateSpanFromContext( + std::string const& spanName, + Azure::Core::Context const& clientContext); + + std::unique_ptr CreateAttributeSet(); + }; + + /** + * @brief Attributes emitted as a part of distributed tracing spans. + * + * List taken from here: + * https://github.com/Azure/azure-sdk/blob/main/docs/tracing/distributed-tracing-conventions.yml + * + */ + class TracingAttributes + : public Azure::Core::_internal::ExtendableEnumeration { + public: + explicit TracingAttributes(std::string const& that) : ExtendableEnumeration(that) {} + + /** + * @brief + * [Namespace](https://docs.microsoft.com/azure/azure-resource-manager/management/azure-services-resource-providers) + * of Azure service request is made against. + * + */ + AZ_CORE_DLLEXPORT const static TracingAttributes AzNamespace; + + /** + * @brief HTTP request method. + * + */ + AZ_CORE_DLLEXPORT const static TracingAttributes HttpMethod; + + /** + * @brief Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. + * + */ + AZ_CORE_DLLEXPORT const static TracingAttributes HttpUrl; + + /** + * @brief [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). + * + */ + AZ_CORE_DLLEXPORT const static TracingAttributes HttpStatusCode; + + /** + * @brief Value of the [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) + * header sent by the client. + * + */ + AZ_CORE_DLLEXPORT const static TracingAttributes HttpUserAgent; + + /** @brief Value of the[x - ms - client - request - id] header(or other request - id header, + * depending on the service) sent by the client. + */ + AZ_CORE_DLLEXPORT const static TracingAttributes RequestId; + + /** @brief Value of the [x-ms-request-id] header (or other request-id header, depending on the + * service) sent by the server in response. + */ + AZ_CORE_DLLEXPORT const static TracingAttributes ServiceRequestId; + }; + +}}}} // namespace Azure::Core::Tracing::_internal diff --git a/sdk/core/azure-core/inc/azure/core/tracing/tracing.hpp b/sdk/core/azure-core/inc/azure/core/tracing/tracing.hpp new file mode 100644 index 0000000000..71663cf4fb --- /dev/null +++ b/sdk/core/azure-core/inc/azure/core/tracing/tracing.hpp @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Handling log messages from Azure SDK. + */ + +#pragma once + +#include "azure/core/datetime.hpp" +#include "azure/core/internal/extendable_enumeration.hpp" +#include "azure/core/nullable.hpp" +#include "azure/core/url.hpp" +#include +#include +#include +#include + +namespace Azure { namespace Core { namespace Tracing { + + namespace _internal { + + /** The set of attributes to be applied to a Span or Event. + * + * @details + * An AttributeSet represents a set of attributes to be added to a span or + * event. + * + * @note Note that AttributeSets do *NOT* take a copy of their input values, + * it is the responsibility of the caller to ensure that the object remains + * valid between when it is added to the AttributeSet and when it is added to + * a span or event. + * + * OpenTelemetry property bags can hold: + * - bool + * - int32_t + * - int64_t + * - uint64_t + * - double + * - const char * + * - std::string/std::string_view *** Not Implemented + * - std::span *** Not Implemented + * - std::span *** Not Implemented + * - std::span *** Not Implemented + * - std::span *** Not Implemented + * - std::span *** Not Implemented + * - std::span *** Not Implemented + * - uint64_t (not fully supported) *** Not Implemented + * - std::span (not fully supported) *** Not Implemented + * - std::span (not fully supported) *** Not Implemented. + * + */ + class AttributeSet { + public: + /** + * @brief Adds a Boolean attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, bool value) = 0; + /** + * @brief Adds a 32bit integer attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, int32_t value) = 0; + /** + * @brief Adds a 64bit integer attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, int64_t value) = 0; + /** + * @brief Adds an unsigned 64bit integer attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, uint64_t value) = 0; + /** + * @brief Adds a 64bit floating point attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, double value) = 0; + /** + * @brief Adds a C style string attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, const char* value) = 0; + /** + * @brief Adds a C++ string attribute to the attribute set. + * + * @param attributeName Name of attribute to add. + * @param value Value of attribute. + */ + virtual void AddAttribute(std::string const& attributeName, std::string const& value) = 0; + + /** + * @brief destroys an AttributeSet - virtual destructor to enable base class users to destroy + * derived classes. + */ + virtual ~AttributeSet(){}; + }; + + /** @brief The Type of Span. + */ + class SpanKind final : public Azure::Core::_internal::ExtendableEnumeration { + public: + explicit SpanKind(std::string const& kind) : ExtendableEnumeration(kind) {} + SpanKind() = default; + + /** + * @brief Represents an "Internal" operation. + * + */ + AZ_CORE_DLLEXPORT const static SpanKind Internal; + /** + * @brief Represents a request to a remote service. + * + */ + AZ_CORE_DLLEXPORT const static SpanKind Client; + /** + * @brief Represents a span covering the server side handling of an API call. + * + */ + AZ_CORE_DLLEXPORT const static SpanKind Server; + /** + * @brief Represents the initiator of an asynchronous request. + * + */ + AZ_CORE_DLLEXPORT const static SpanKind Producer; + /** + * @brief Represents a span which describes a child of a producer request. + * + */ + AZ_CORE_DLLEXPORT const static SpanKind Consumer; + }; + + /** + * @brief Represents the status of a span. + */ + class SpanStatus final : public Azure::Core::_internal::ExtendableEnumeration { + + public: + explicit SpanStatus(std::string const& status) : ExtendableEnumeration(status) {} + SpanStatus() = default; + + /** + * @brief The default status of a span. + */ + AZ_CORE_DLLEXPORT const static SpanStatus Unset; + /** + * @brief The operation has completed successfully. + */ + AZ_CORE_DLLEXPORT const static SpanStatus Ok; + /** + * @brief The operation contains an error. + */ + AZ_CORE_DLLEXPORT const static SpanStatus Error; + }; + + /** + * @brief Span - represents a span in tracing. + */ + class Span { + public: + /** + * @brief Signals that the span has now ended. + */ + virtual void End(Azure::Nullable endTime = {}) = 0; + + /** + * @brief Adds a set of attributes to the span. + * + * @param attributeToAdd Attributes to be added to the span. + */ + virtual void AddAttributes(AttributeSet const& attributeToAdd) = 0; + + /** + * @brief Adds an event to the span. + * + * Add an Event to the span. An event is identified by a name and an optional set of + * attributes associated with the event. + * + * @param eventName Name of the event to add. + * @param eventAttributes Attributes associated with the event. + */ + virtual void AddEvent(std::string const& eventName, AttributeSet const& eventAttributes) = 0; + + /** + * @brief Adds an event to the span. + * + * Add an Event to the span. An event is identified by a name + * + * @param eventName Name of the event to add. + */ + virtual void AddEvent(std::string const& eventName) = 0; + /** + * @brief Records an exception occurring in the span. + * + * @param exception Exception which has occurred. + */ + virtual void AddEvent(std::exception const& exception) = 0; + + /** + * @brief Set the Status of the span + * + * @param status Updated status of the span. + * @param description A description associated with the Status. + */ + virtual void SetStatus(SpanStatus const& status, std::string const& description = "") = 0; + }; + + /** + * @brief Options used while creating a span. + * + */ + struct CreateSpanOptions final + { + /** + * @brief The kind of span to be created. + * + */ + SpanKind Kind{SpanKind::Internal}; + /** + * @brief Attributes associated with the span. + * + */ + std::unique_ptr Attributes; + + /** + * @brief Parent for the newly created span. + */ + std::shared_ptr ParentSpan; + }; + + /** + * @brief Tracer - factory for creating span objects. + * + */ + class Tracer { + public: + /** + * @brief Create new Span object. + * + * @details Creates a new span object. + * + * @note There is no concept of a "current" span, each span created is a top level span, + * unless the CreateSpanOptions has ParentSpan member, in which case the ParentSpan member + * will be the parent of the newly created span. + * + * @param spanName The name of the span to create. + * @param options Options to be used when creating the span. + * @return std::shared_ptr Newly created span. + */ + virtual std::shared_ptr CreateSpan( + std::string const& spanName, + CreateSpanOptions const& options = {}) const = 0; + + virtual std::unique_ptr CreateAttributeSet() const = 0; + }; + } // namespace _internal + + /** + * @brief Trace Provider - factory for creating Tracer objects. + */ + class TracerProvider { + public: + /** + * @brief Create a Tracer object + * + * @param name Name of the tracer object, typically the name of the Service client + * (Azure.Storage.Blobs, for example) + * @param version Version of the service client. + * @return std::shared_ptr + */ + virtual std::shared_ptr CreateTracer( + std::string const& name, + std::string const& version = "") const = 0; + }; +}}} // namespace Azure::Core::Tracing diff --git a/sdk/core/azure-core/src/tracing/tracing.cpp b/sdk/core/azure-core/src/tracing/tracing.cpp new file mode 100644 index 0000000000..d36a5301a3 --- /dev/null +++ b/sdk/core/azure-core/src/tracing/tracing.cpp @@ -0,0 +1,115 @@ +#include "azure/core/tracing/tracing.hpp" +#include "azure/core/internal/tracing/service_tracing.hpp" + +namespace Azure { namespace Core { namespace Tracing { namespace _internal { + + const SpanKind SpanKind::Internal("Internal"); + const SpanKind SpanKind::Client("Client"); + const SpanKind SpanKind::Consumer("Consumer"); + const SpanKind SpanKind::Producer("Producer"); + const SpanKind SpanKind::Server("Server"); + + const SpanStatus SpanStatus::Unset("Unset"); + const SpanStatus SpanStatus::Ok("Ok"); + const SpanStatus SpanStatus::Error("Error"); + + const TracingAttributes TracingAttributes::AzNamespace("az.namespace"); + + DiagnosticTracingFactory::ContextAndSpan DiagnosticTracingFactory::CreateSpan( + std::string const& methodName, + Azure::Core::Context const& context) + { + CreateSpanOptions createOptions; + if (m_serviceTracer) + { + Azure::Core::Context contextToUse = context; + + // Ensure that the factory is available in the context chain. + DiagnosticTracingFactory* tracingFactoryFromContext; + if (!context.TryGetValue(TracingFactoryContextKey, tracingFactoryFromContext)) + { + contextToUse = context.WithValue(TracingFactoryContextKey, this); + } + + TracingContext traceContext; + // Find a span in the context hierarchy. + if (contextToUse.TryGetValue(ContextSpanKey, traceContext)) + { + createOptions.ParentSpan = traceContext; + } + else + { + // Please note: Not specifically needed, but make sure that this is a root level + // span if there is no parent span in the context + createOptions.ParentSpan = nullptr; + } + createOptions.Attributes = m_serviceTracer->CreateAttributeSet(); + createOptions.Attributes->AddAttribute( + TracingAttributes::AzNamespace.ToString(), m_serviceName); + + std::shared_ptr newSpan(m_serviceTracer->CreateSpan(methodName, createOptions)); + TracingContext tracingContext = newSpan; + Azure::Core::Context newContext = contextToUse.WithValue(ContextSpanKey, tracingContext); + ServiceSpan newServiceSpan(newSpan); + return std::make_pair( + std::move(newContext), std::move(newServiceSpan)); + } + else + { + return std::make_pair(context, ServiceSpan{}); + } + } + DiagnosticTracingFactory::ContextAndSpan DiagnosticTracingFactory::CreateSpanFromContext( + std::string const& spanName, + Azure::Core::Context const& context) + { + DiagnosticTracingFactory* tracingFactory + = DiagnosticTracingFactory::DiagnosticFactoryFromContext(context); + if (tracingFactory) + { + return tracingFactory->CreateSpan(spanName, context); + } + else + { + return std::make_pair(context, ServiceSpan{}); + } + } + + Azure::Nullable + DiagnosticTracingFactory::TracingContextFromContext(Azure::Core::Context const& context) + { + TracingContext traceContext; + if (context.TryGetValue(ContextSpanKey, traceContext)) + { + return traceContext; + } + else + { + return Azure::Nullable{}; + } + } + + DiagnosticTracingFactory* DiagnosticTracingFactory::DiagnosticFactoryFromContext( + Azure::Core::Context const& context) + { + DiagnosticTracingFactory* factory; + if (context.TryGetValue(TracingFactoryContextKey, factory)) + { + return factory; + } + else + { + return nullptr; + } + } + + std::unique_ptr + DiagnosticTracingFactory::CreateAttributeSet() + { + return m_serviceTracer->CreateAttributeSet(); + } + + Azure::Core::Context::Key DiagnosticTracingFactory::ContextSpanKey; + Azure::Core::Context::Key DiagnosticTracingFactory::TracingFactoryContextKey; + +}}}} // namespace Azure::Core::Tracing::_internal diff --git a/sdk/core/azure-core/test/ut/CMakeLists.txt b/sdk/core/azure-core/test/ut/CMakeLists.txt index 30f1a22258..f50f787a1a 100644 --- a/sdk/core/azure-core/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core/test/ut/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable ( environment_log_level_listener_test.cpp extendable_enumeration_test.cpp etag_test.cpp + exception_test.cpp http_test.cpp http_test.hpp http_method_test.cpp @@ -68,6 +69,7 @@ add_executable ( request_id_policy_test.cpp response_t_test.cpp retry_policy_test.cpp + service_tracing_test.cpp sha_test.cpp simplified_header_test.cpp string_test.cpp @@ -77,7 +79,6 @@ add_executable ( transport_adapter_implementation_test.cpp url_test.cpp uuid_test.cpp - exception_test.cpp ) if (MSVC) diff --git a/sdk/core/azure-core/test/ut/context_test.cpp b/sdk/core/azure-core/test/ut/context_test.cpp index 17102d94e7..fc99cca6af 100644 --- a/sdk/core/azure-core/test/ut/context_test.cpp +++ b/sdk/core/azure-core/test/ut/context_test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -30,7 +31,7 @@ TEST(Context, BasicBool) // New context from previous auto c2 = context.WithValue(key, true); - bool value; + bool value{}; EXPECT_TRUE(c2.TryGetValue(key, value)); EXPECT_TRUE(value == true); @@ -513,3 +514,26 @@ TEST(Context, KeyTypePairPrecondition) EXPECT_TRUE(c3.TryGetValue(key, strValue)); EXPECT_TRUE(strValue == s); } + +TEST(Context, SetTracingProvider) +{ + class TestTracingProvider final : public Azure::Core::Tracing::TracerProvider { + public: + TestTracingProvider() : TracerProvider() {} + ~TestTracingProvider() {} + std::shared_ptr CreateTracer( + std::string const&, + std::string const&) const override + { + throw std::runtime_error("Not implemented"); + }; + }; + + Context context; + context.SetTracerProvider(nullptr); + + // Verify we can round trip a tracing provider through the context. + auto testProvider = std::make_shared(); + context.SetTracerProvider(testProvider); + EXPECT_EQ(testProvider, context.GetTracerProvider()); +} diff --git a/sdk/core/azure-core/test/ut/service_tracing_test.cpp b/sdk/core/azure-core/test/ut/service_tracing_test.cpp new file mode 100644 index 0000000000..68e205134d --- /dev/null +++ b/sdk/core/azure-core/test/ut/service_tracing_test.cpp @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/core/tracing/tracing.hpp" +#include +#include + +using namespace Azure::Core; +using namespace Azure::Core::Tracing; +using namespace Azure::Core::Tracing::_internal; + +TEST(DiagnosticTracingFactory, ServiceTraceEnums) +{ + // Exercise the SpanKind and SpanStatus constructors from the distributed tracing header. + { + SpanKind spanKind = Azure::Core::Tracing::_internal::SpanKind::Client; + spanKind = SpanKind::Consumer; + spanKind = SpanKind::Internal; + spanKind = SpanKind::Producer; + spanKind = Azure::Core::Tracing::_internal::SpanKind::Server; + std::string kindValue = spanKind.ToString(); + } + { + SpanStatus spanStatus = SpanStatus::Unset; + spanStatus = SpanStatus::Error; + spanStatus = SpanStatus::Ok; + std::string statusValue = spanStatus.ToString(); + } + Azure::Core::Tracing::_internal::CreateSpanOptions options; + options.Kind = SpanKind::Internal; + + std::string tracingAttributeName = TracingAttributes::AzNamespace.ToString(); +} + +TEST(DiagnosticTracingFactory, SimpleServiceSpanTests) +{ + { + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace; + } + { + Azure::Core::_internal::ClientOptions clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + } + + { + Azure::Core::_internal::ClientOptions clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + + auto contextAndSpan = serviceTrace.CreateSpan("My API", {}); + EXPECT_FALSE(contextAndSpan.first.IsCancelled()); + } +} + +// Dummy service tracing class. +class TestSpan final : public Azure::Core::Tracing::_internal::Span { +public: + TestSpan() : Azure::Core::Tracing::_internal::Span() {} + + // Inherited via Span + virtual void AddAttributes(AttributeSet const&) override {} + virtual void AddEvent(std::string const&, AttributeSet const&) override {} + virtual void AddEvent(std::string const&) override {} + virtual void AddEvent(std::exception const&) override {} + virtual void SetStatus(SpanStatus const&, std::string const&) override {} + + // Inherited via Span + virtual void End(Azure::Nullable) override {} +}; + +class TestAttributeSet : public Azure::Core::Tracing::_internal::AttributeSet { +public: + TestAttributeSet() : Azure::Core::Tracing::_internal::AttributeSet() {} + + // Inherited via AttributeSet + virtual void AddAttribute(std::string const&, bool) override {} + virtual void AddAttribute(std::string const&, int32_t) override {} + virtual void AddAttribute(std::string const&, int64_t) override {} + virtual void AddAttribute(std::string const&, uint64_t) override {} + virtual void AddAttribute(std::string const&, double) override {} + virtual void AddAttribute(std::string const&, const char*) override {} + virtual void AddAttribute(std::string const&, std::string const&) override {} +}; +class TestTracer final : public Azure::Core::Tracing::_internal::Tracer { +public: + TestTracer(std::string const&, std::string const&) : Azure::Core::Tracing::_internal::Tracer() {} + std::shared_ptr CreateSpan(std::string const&, CreateSpanOptions const&) const override + { + return std::make_shared(); + } + + std::unique_ptr CreateAttributeSet() const override + { + return std::make_unique(); + }; +}; + +class TestTracingProvider final : public Azure::Core::Tracing::TracerProvider { +public: + TestTracingProvider() : TracerProvider() {} + ~TestTracingProvider() {} + std::shared_ptr CreateTracer( + std::string const& serviceName, + std::string const& serviceVersion) const override + { + return std::make_shared(serviceName, serviceVersion); + }; +}; + +TEST(DiagnosticTracingFactory, BasicServiceSpanTests) +{ + { + Azure::Core::_internal::ClientOptions clientOptions; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + + auto contextAndSpan = serviceTrace.CreateSpan("My API", {}); + auto span = std::move(contextAndSpan.second); + + span.End(); + span.AddEvent("New Event"); + span.AddEvent(std::runtime_error("Exception")); + span.SetStatus(SpanStatus::Error); + } + { + Azure::Core::_internal::ClientOptions clientOptions; + auto testTracer = std::make_shared(); + clientOptions.Telemetry.TracingProvider = testTracer; + Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace( + clientOptions, "my-service-cpp", "1.0b2"); + + auto contextAndSpan = serviceTrace.CreateSpan("My API", {}); + auto span = std::move(contextAndSpan.second); + + span.End(); + span.AddEvent("New Event"); + span.AddEvent(std::runtime_error("Exception")); + std::unique_ptr attributeSet + = serviceTrace.CreateAttributeSet(); + attributeSet->AddAttribute("Joe", "Joe'sValue"); + span.AddEvent("AttributeEvent", *attributeSet); + span.AddAttributes(*attributeSet); + span.SetStatus(SpanStatus::Error); + } +} diff --git a/sdk/template/azure-template/inc/azure/template/template_client.hpp b/sdk/template/azure-template/inc/azure/template/template_client.hpp index dc17db0822..e9821dc1b1 100644 --- a/sdk/template/azure-template/inc/azure/template/template_client.hpp +++ b/sdk/template/azure-template/inc/azure/template/template_client.hpp @@ -3,12 +3,19 @@ #pragma once +#include +#include #include namespace Azure { namespace Template { + struct TemplateClientOptions : public Azure::Core::_internal::ClientOptions + { + }; class TemplateClient final { + public: + TemplateClient(TemplateClientOptions options = TemplateClientOptions()); std::string ClientVersion() const; int GetValue(int key) const; }; diff --git a/sdk/template/azure-template/src/template_client.cpp b/sdk/template/azure-template/src/template_client.cpp index 9443847ab4..794349b07c 100644 --- a/sdk/template/azure-template/src/template_client.cpp +++ b/sdk/template/azure-template/src/template_client.cpp @@ -12,6 +12,8 @@ using namespace Azure::Template::_detail; std::string TemplateClient::ClientVersion() const { return PackageVersion::ToString(); } +TemplateClient::TemplateClient(TemplateClientOptions) {} + int TemplateClient::GetValue(int key) const { if (key < 0) diff --git a/vcpkg.json b/vcpkg.json index baec3a149e..3a5459cca4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,7 @@ { "name": "azure-sdk-for-cpp", "version": "1.5.0", + "builtin-baseline": "f0aa678b7471497f1adedcc99f40e1599ad22f69", "dependencies": [ { "name": "curl" @@ -11,6 +12,11 @@ }, { "name": "openssl" + }, + { + "name": "opentelemetry-cpp", + "platform": "!uwp", + "version>=": "1.3.0" } ] }