Complete the initial implementation of OpenTelemetry (#3677)

* Added telemetry support for HTTP pipeline elements

* Finish OpenTelemetry implementation

* clang-format and added doxygen comments
This commit is contained in:
Larry Osterman 2022-05-31 10:57:32 -07:00 committed by GitHub
parent 855f000f63
commit 7c93854125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1260 additions and 223 deletions

1
.vscode/cspell.json vendored
View File

@ -92,6 +92,7 @@
"northcentralus",
"NTSTATUS",
"okhttp",
"opentelemetry",
"otel",
"PBYTE",
"pdbs",

250
doc/DistributedTracing.md Normal file
View File

@ -0,0 +1,250 @@
---
# cspell:words openetelemetry
---
# Distributed Tracing in the C++ SDK
Azure has adopted [W3C Distributed Tracing](https://www.w3.org/TR/trace-context/) as a paradigm for correlating
requests from clients across multiple services.
This document explains how the Azure C++ SDK implements distributed tracing, how clients integrate with distributed tracing, how
services should integrate with distributed tracing and finally how the network pipeline and other functionality should
integrate with distributed tracing.
## Tracing Overview
The Azure SDK for C++ Tracing APIs are modeled after the opentelemetry-cpp API surface defined in the [OpenTelemetry Tracing Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md).
Additional architectural information about OpenTelemetry can be found in [OpenTelemetry Concepts](https://opentelemetry.io/docs/concepts/).
There are three major components which the Azure SDK components interact with:
- `TracerProvider` - this is a factory which creates `Tracer` objects.
- `Tracer` - this is a factory which creates `Span` objects.
- `Span` - Span objects are the APIs which allow tracing an operation.
Each `span` has a name, a type and a "status". `Spans` also contain "attributes" and "events" which describe an operation.
There is typically a single `TracerProvider` for each application, and for the Azure SDK, each
service will have a `Tracer` implementation which creates `Span` objects for each service client.
A `Span` can be considered a "unit of work" for a service. Each service method (method which calls into the service) will have a single `Span` reflecting the client method which
was called.
`Span`'s are hierarchical and each span can have multiple children (each `Span` can only have a single parent). The typical way that this manifests itself during a
service method call is:
- Service Method "MyServiceMethod" creates a span named "MyServiceMethod" and starts an HTTP request to communicate with the service.
- The HTTP pipeline (specifically the `RequestActivityPolicy`) will create a child `span` under the service method `span` named `"HTTP <verb> #0"`. This span
reflects the HTTP call into the service.
- If the HTTP call needs to be retried, the existing `span` will be closed an a new span named `HTTP <verb> #1` will be created for the retry.
## Distributed Tracing Client Integration
Applications which wish to integrate Distributed Tracing are strongly encouraged
to use the [opentelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) vcpkg package.
There are numerous examples on the OpenTelemetry web site which demonstrate how to integrate
opentelemetry into a customer application and integrate the generated traces
with Azure monitoring infrastructure such as Geneva Monitoring.
Following the examples from opentelemetry-cpp, the following can be used
to establish an OpenTelemetry exporter which logs to the console or to an
in-memory logger.
```c++
opentelemetry::nostd::shared_ptr<opentelemetry::trace::TracerProvider>
CreateOpenTelemetryProvider()
{
#if USE_MEMORY_EXPORTER
auto exporter = std::make_unique<opentelemetry::exporter::memory::InMemorySpanExporter>();
#else
auto exporter = std::make_unique<opentelemetry::exporter::trace::OStreamSpanExporter>();
#endif
// simple processor
auto simple_processor = std::unique_ptr<opentelemetry::sdk::trace::SpanProcessor>(
new opentelemetry::sdk::trace::SimpleSpanProcessor(std::move(exporter)));
auto always_on_sampler = std::unique_ptr<opentelemetry::sdk::trace::AlwaysOnSampler>(
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<opentelemetry::trace::TracerProvider>(
new opentelemetry::sdk::trace::TracerProvider(
std::move(simple_processor), resource, std::move(always_on_sampler)));
}
```
Other exporters exist to export to [Jaeger](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/exporters/jaeger),
[Windows ETW](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/exporters/etw) and others.
Once the `opentelemetry::trace::TracerProvider` has been created, The client needs to create a new `Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider` which
functions as an abstract class integration between OpenTelemetry and Azure Core:
```c++
std::shared_ptr<Azure::Core::Tracing::TracerProvider> traceProvider
= std::make_shared<Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider>(CreateOpenTelemetryProvider());
```
To finish the integration with Azure clients, there are two mechanisms to integrate OpenTelemetry into a client application:
1) `Azure::Core::Context` integration.
1) Service Client Options integration.
### Integrate an OpenTelemetryProvider via the ApplicationContext
To integrate OpenTelemetry for all Azure Clients in the application, the customer can call `Azure::Core::Context::ApplicationContext.SetTracerProvider` to establish the
tracer provider for the application.
```c++
Azure::Core::Context::ApplicationContext.SetTracerProvider(provider);
```
### Integrate an OpenTelemetryProvider via Service ClientOptions
While using the ApplicationContext is the simplest mechanism for integration OpenTelemetry with a customer application, there may be times the customer needs more flexibility when creating service clients.
To enable customers to further customize how tracing works, the application can set the `Telemetry.TracingProvider` field in the service client options, which will establish the tracer provider used by
the service client.
```c++
auto tracerProvider(CreateOpenTelemetryProvider());
auto provider(std::make_shared<Azure::Core::Tracing::OpenTelemetry::OpenTelemetryProvider>(tracerProvider));
ServiceClientOptions clientOptions;
clientOptions.Telemetry.TracingProvider = provider;
clientOptions.Telemetry.ApplicationId = "MyApplication";
ServiceClient myServiceClient(clientOptions);
```
## Distributed Tracing Service Integration
There are two steps needed to integrate Distributed Tracing with a Service Client.
1. Add a `DiagnosticTracingFactory` object to the ServiceClient object
1. Update each service method as follows:
1. Add a call to the `CreateSpan` method on the diagnostic tracing factory. This will create a new span for the client operation.
1. Call `SetStatus` on the created span when the service method successfully completes.
1. Wrap the client method code with a try/catch handler which catches exceptions and call AddEvent with the value of the exception.
### Add a `DiagnosticTracingFactory` to the serviceClient class
To add a new `DiagnosticTracingFactory` to the client, simply add the class as a member:
```c++
Azure::Core::Tracing::_internal::DiagnosticTracingFactory m_tracingFactory;
```
And construct the new tracing factory in the service constructor:
```c++
explicit ServiceClient(ServiceClientOptions const& clientOptions = ServiceClientOptions{})
: m_tracingFactory(clientOptions, "Azure.Core.OpenTelemetry.Test.Service", PackageVersion::ToString())
```
### Update Each Service Method
There are three methods of interest when updating the service method:
1. `DiagnosticTracingFactory::CreateSpan` - this creates and returns a `Span` and `Context` object for the service method. The returned Context object must be used for subsequent service operations.
1. `Span::AddEvent(std::exception&)` - This registers the exception with the distributed tracing infrastructure.
1. `Span::SetStatus` - This sets the status of the operation in the trace.
```c++
Azure::Response<std::string> ServiceMethod(
std::string const&,
Azure::Core::Context const& context = Azure::Core::Context{})
{
// Create a new context and span for this request.
auto contextAndSpan = m_tracingFactory.CreateSpan(
"ServiceMethod", Azure::Core::Tracing::_internal::SpanKind::Internal, context);
// contextAndSpan.first is the new context for the operation.
// contextAndSpan.second is the new span for the operation.
try
{
// <Call Into Service via an HTTP pipeline>
Azure::Core::Http::Request requestToSend(
HttpMethod::Get, Azure::Core::Url("<Service URL>"));
std::unique_ptr<Azure::Core::Http::RawResponse> response
= m_pipeline->Send(requestToSend, contextAndSpan.first);
contextAndSpan.second.SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Ok);
return Azure::Response<std::string>("", std::move(response));
}
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.
}
};
```
## Implementation Details
### Distributed Tracing components
In order to maintain flexibility, the opentelemetry-cpp APIs are implemented in a separate package - azure-core-tracing-opentelemetry.
This is consistent with how opentelemetry is distributed for
the other Azure SDKs.
The Azure Core API surface interacts with a set of pure virtual base classes (aka "interfaces") in
the `Azure::Core::Tracing` and `Azure::Core::Tracing::_internal` namespace. These allow a level of separation
between the Azure Core API surface and the OpenTelemetry API surface - an alternative tracing mechanism needs
to provide APIs consistent with the `Azure::Core::Tracing` APIs.
The azure-core-tracing-openetelemetry-cpp package implements a set of APIs in the `Azure::Core::Tracing::OpenTelemetry`
and `Azure::Core::Tracing::OpenTelemetry::_detail` namespace. These provide an Azure Core compatable API surface for distributed tracing.
The core service client interface is the `DiagnosticTracingFactory` class which implements two APIs: `CreateSpan` and
`CreateSpanFromContext`. `CreateSpan` is intended to be used by service methods which have direct access to a
`DiagnosticTracingFactory` object, `CreateSpanFromContext` in intended to be used from code which does NOT have
direct access to the `DiagnosticTracingFactory`.
The final significant piece of the distributed tracing infrastructure is the `RequestActivityPolicy` - this policy MUST be
inserted into the HTTP pipeline AFTER the `RetryPolicy`. It is responsible for creating the span associated with the HTTP request, it will
also propagate the W3C distributed tracing headers from the span into the HTTP request.
### Generated traces
The Azure standards for distributed tracing are define in [Azure Distributed Tracing Conventions](https://github.com/Azure/azure-sdk/blob/main/docs/tracing/distributed-tracing-conventions.md).
The actual tracing elements generated by Azure services are defined in [Azure Tracing Conventions YAML](https://github.com/Azure/azure-sdk/blob/main/docs/tracing/distributed-tracing-conventions.yml).
In summary, these are the traces and attributes which should be generated
for azure services:
#### Spans
The distributed tracing standards define the following traces:
##### Public APIs
All public APIs MUST create a span which will describes the API.
The name of the span MUST be the API name.
##### HTTP Calls
Each HTTP request sent to the service MUST create a span describing the request to the service.
The name of the span MUST be of the form `HTTP <HTTP VERB> #<HTTP RETRY>`.
#### Attributes
Generated traces have the following attributes:
| Attribute Name | Semantics | Where Used
|-----------|--------|-------
| `az.namespace` |Namespace of the azure service request| All spans.
| `http.method`| HTTP Method ("GET", "PUT", etc)| HTTP Spans.
| `http.url`| URL being retrieved (sanitized)| HTTP Spans.
| `http.status_code` | HTTP status code returned by the service | HTTP Spans.
| `http.user_agent` | The value of the `User-Agent` HTTP header sent to the service | HTTP Spans.
| `requestId` | The value of the `x-ms-client-request-id` header sent by the client | HTTP Spans.
| `serviceRequestId` | The value -f the `x-ms-request-id` sent by the server | HTTP Spans.

View File

@ -73,15 +73,15 @@
"Win_x86_with_unit_test_winHttp": {
"VcpkgInstall": "openssl",
"CMAKE_GENERATOR_PLATFORM": "Win32",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON ",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DMSVC_USE_STATIC_CRT=ON ",
"VCPKG_DEFAULT_TRIPLET": "x86-windows-static",
"WindowsCtestConfig": "-C Release",
"BuildArgs": "-v --parallel 8 --config Release"
},
"Win_x86_no_rtti_whit_unit_test": {
"Win_x86_no_rtti_with_unit_test": {
"VcpkgInstall": "libxml2 openssl",
"CMAKE_GENERATOR_PLATFORM": "Win32",
"CmakeArgs": " -DBUILD_RTTI=OFF -DBUILD_TESTING=ON -DBUILD_SAMPLES=ON",
"CmakeArgs": " -DBUILD_RTTI=OFF -DBUILD_TESTING=ON -DBUILD_SAMPLES=ON -DMSVC_USE_STATIC_CRT=ON",
"VCPKG_DEFAULT_TRIPLET": "x86-windows-static",
"WindowsCtestConfig": "-C Release",
"BuildArgs": "-v --parallel 8 --config Release"
@ -89,15 +89,33 @@
"Win_x86_with_unit_test_libcurl": {
"CMAKE_GENERATOR_PLATFORM": "Win32",
"VCPKG_DEFAULT_TRIPLET": "x86-windows-static",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON ",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DMSVC_USE_STATIC_CRT=ON ",
"BuildArgs": "-v --parallel 8"
},
"Win_x64_with_json_unit_test_winHttp": {
"VcpkgInstall": "openssl",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DDISABLE_AZURE_CORE_OPENTELEMETRY=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"AZURE_CORE_ENABLE_JSON_TESTS": 1,
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"WindowsCtestConfig": "-C Release"
},
"Win_x64_with_json_unit_samples_winHttp": {
"VcpkgInstall": "openssl",
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DBUILD_SAMPLES=ON -DDISABLE_AZURE_CORE_OPENTELEMETRY=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"AZURE_CORE_ENABLE_JSON_TESTS": 1,
"RunSamples": 1,
"WindowsCtestConfig": "-C Release"
},
"Win_x64_with_unit_test_winHttp": {
"VcpkgInstall": "openssl",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON ",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DMSVC_USE_STATIC_CRT=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"AZURE_CORE_ENABLE_JSON_TESTS": 1,
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"WindowsCtestConfig": "-C Release"
},
@ -105,16 +123,15 @@
"VcpkgInstall": "openssl",
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DBUILD_SAMPLES=ON ",
"CmakeArgs": " -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DBUILD_SAMPLES=ON -DMSVC_USE_STATIC_CRT=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"AZURE_CORE_ENABLE_JSON_TESTS": 1,
"RunSamples": 1,
"WindowsCtestConfig": "-C Release"
},
"Win_x64_with_unit_test_libcurl": {
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON ",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DMSVC_USE_STATIC_CRT=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"WindowsCtestConfig": "-C Release"
},
@ -122,7 +139,7 @@
"VcpkgInstall": "curl[winssl] openssl",
"VCPKG_DEFAULT_TRIPLET": "x64-windows-static",
"CMAKE_GENERATOR_PLATFORM": "x64",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DBUILD_SAMPLES=ON ",
"CmakeArgs": " -DBUILD_TRANSPORT_CURL=ON -DBUILD_TESTING=ON -DRUN_LONG_UNIT_TESTS=ON -DBUILD_PERFORMANCE_TESTS=ON -DBUILD_SAMPLES=ON -DMSVC_USE_STATIC_CRT=ON ",
"BuildArgs": "-v --parallel 8 --config Release",
"RunSamples": 1,
"WindowsCtestConfig": "-C Release"

View File

@ -1,3 +1,7 @@
---
# cspell:words opentelemetry
---
# Azure Attestation Package client library for C++
Microsoft Azure Attestation is a unified solution for remotely verifying the trustworthiness of a platform and integrity of the binaries running inside it. The service supports attestation of the platforms backed by Trusted Platform Modules (TPMs) alongside the ability to attest to the state of Trusted Execution Environments (TEEs) such as Intel(tm) Software Guard Extensions (SGX) enclaves and Virtualization-based Security (VBS) enclaves.

View File

@ -42,7 +42,7 @@
"metadata": {
"description": "The application client secret used to run tests."
}
},
}
},
"variables": {
"isolatedTenantName": "[concat('cp', concat(parameters('baseName'), 'iso'))]",
@ -66,7 +66,7 @@
"type": "Microsoft.Attestation/attestationProviders",
"apiVersion": "2020-10-01",
"name": "[variables('aadTenantName')]",
"location": "[parameters('location')]",
"location": "[parameters('location')]"
},
{
"type": "Microsoft.Attestation/attestationProviders",

View File

@ -2,4 +2,6 @@
## 1.0.0-beta.1 (Unreleased)
### Features Added
- Initial release

View File

@ -56,7 +56,7 @@ target_include_directories(
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?/*")
create_code_coverage(core azure-core-tracing-opentelemetry azure-core-tracing-opentelemetry-test "tests?/*;samples?/*")
target_link_libraries(azure-core-tracing-opentelemetry INTERFACE Threads::Threads)

View File

@ -11,6 +11,7 @@
#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4244)
#pragma warning(disable : 6323) // Disable "Use of arithmetic operator on Boolean type" warning.
#endif
#include <opentelemetry/common/kv_properties.h>
#include <opentelemetry/trace/provider.h>
@ -114,6 +115,8 @@ namespace Azure { namespace Core { namespace Tracing { namespace OpenTelemetry {
virtual void AddAttributes(
Azure::Core::Tracing::_internal::AttributeSet const& attributeToAdd) override;
virtual void AddAttribute(std::string const& attributeName, std::string const& attributeValue)
override;
/**
* Add an Event to the span. An event is identified by a name and an optional set of
@ -129,6 +132,14 @@ namespace Azure { namespace Core { namespace Tracing { namespace OpenTelemetry {
Azure::Core::Tracing::_internal::SpanStatus const& status,
std::string const& statusMessage) override;
/**
* @brief Propogate information from the current span to the HTTP request headers.
*
* @param request HTTP Request to the service. If there is an active tracing span, this will
* add required headers to the HTTP Request.
*/
virtual void PropagateToHttpHeaders(Azure::Core::Http::Request& request) override;
opentelemetry::trace::SpanContext GetContext() { return m_span->GetContext(); }
};

View File

@ -1,5 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/tracing/opentelemetry/opentelemetry.hpp"
#include <azure/core/http/http.hpp>
#include <azure/core/nullable.hpp>
#include <azure/core/tracing/tracing.hpp>
#include <memory>
@ -9,7 +12,9 @@
#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4244)
#pragma warning(disable : 6323)
#endif
#include <opentelemetry/trace/propagation/http_trace_context.h>
#include <opentelemetry/trace/provider.h>
#include <opentelemetry/trace/tracer_provider.h>
#if defined(_MSC_VER)
@ -185,5 +190,67 @@ namespace Azure { namespace Core { namespace Tracing { namespace OpenTelemetry {
m_span->SetStatus(statusCode, statusMessage);
}
void OpenTelemetrySpan::AddAttribute(
std::string const& attributeName,
std::string const& attributeValue)
{
m_span->SetAttribute(attributeName, opentelemetry::common::AttributeValue(attributeValue));
}
/**
* @brief Text map propagator used to read or write properties from an HTTP request.
*
* @details OpenTelemetry defines a `TextMapCarrier` class as a class which allows reading and
* writing to a map of text elements. The OpenTelemetry
* [HttpTraceContext](https://opentelemetry-cpp.readthedocs.io/en/latest/otel_docs/classopentelemetry_1_1trace_1_1propagation_1_1HttpTraceContext.html)
* uses a TextMapCarrier to propogate the required HTTP headers from an OpenTelemetry context
* into an HTTP request.
*/
class HttpRequestTextMapPropagator
: public opentelemetry::context::propagation::TextMapCarrier {
Azure::Core::Http::Request& m_request;
// Inherited via TextMapCarrier
/** @brief Retrieves the value of an HTTP header from the request.
*/
virtual opentelemetry::nostd::string_view Get(
opentelemetry::nostd::string_view key) const noexcept override
{
auto header = m_request.GetHeader(std::string(key));
if (header)
{
return header.Value();
}
return std::string();
}
/** @brief Sets the value of an HTTP header in the request.
*/
virtual void Set(
opentelemetry::nostd::string_view key,
opentelemetry::nostd::string_view value) noexcept override
{
m_request.SetHeader(std::string(key), std::string(value));
}
public:
HttpRequestTextMapPropagator(Azure::Core::Http::Request& request) : m_request(request) {}
};
void OpenTelemetrySpan::PropagateToHttpHeaders(Azure::Core::Http::Request& request)
{
if (m_span)
{
HttpRequestTextMapPropagator propagator(request);
// Establish the current runtime context from the span.
auto scope = opentelemetry::trace::Tracer::WithActiveSpan(m_span);
auto currentContext = opentelemetry::context::RuntimeContext::GetCurrent();
// And inject all required headers into the Request.
opentelemetry::trace::propagation::HttpTraceContext().Inject(propagator, currentContext);
}
}
} // namespace _detail
}}}} // namespace Azure::Core::Tracing::OpenTelemetry

View File

@ -11,6 +11,7 @@
#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4244)
#pragma warning(disable : 6323) // Disable "Use of arithmetic operator on Boolean type" warning.
#endif
#include <opentelemetry/exporters/memory/in_memory_span_data.h>
#include <opentelemetry/exporters/memory/in_memory_span_exporter.h>

View File

@ -4,6 +4,7 @@
#define USE_MEMORY_EXPORTER 1
#include "azure/core/internal/tracing/service_tracing.hpp"
#include "azure/core/tracing/opentelemetry/opentelemetry.hpp"
#include <azure/core/internal/http/pipeline.hpp>
#include <azure/core/internal/json/json.hpp>
#include <azure/core/test/test_base.hpp>
@ -13,6 +14,7 @@
#pragma warning(push)
#pragma warning(disable : 4100)
#pragma warning(disable : 4244)
#pragma warning(disable : 6323) // Disable "Use of arithmetic operator on Boolean type" warning.
#endif
#include <opentelemetry/exporters/memory/in_memory_span_data.h>
#include <opentelemetry/exporters/memory/in_memory_span_exporter.h>
@ -22,11 +24,17 @@
#include <opentelemetry/sdk/trace/processor.h>
#include <opentelemetry/sdk/trace/simple_processor.h>
#include <opentelemetry/sdk/trace/tracer_provider.h>
#include <opentelemetry/trace/propagation/http_trace_context.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#include <chrono>
#include <gtest/gtest.h>
#include <regex>
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http::Policies::_internal;
using namespace Azure::Core::Http;
class CustomLogHandler : public opentelemetry::sdk::common::internal_log::LogHandler {
void Handle(
@ -165,7 +173,7 @@ protected:
switch (span->GetSpanKind())
{
case opentelemetry::trace::SpanKind::kClient:
EXPECT_EQ(expectedKind, "internal");
EXPECT_EQ(expectedKind, "client");
break;
case opentelemetry::trace::SpanKind::kConsumer:
EXPECT_EQ(expectedKind, "consumer");
@ -207,7 +215,11 @@ protected:
case opentelemetry::common::kTypeString: {
EXPECT_TRUE(expectedAttributes[foundAttribute.first].is_string());
const auto& actualVal = opentelemetry::nostd::get<std::string>(foundAttribute.second);
EXPECT_EQ(expectedAttributes[foundAttribute.first].get<std::string>(), actualVal);
std::string expectedVal(expectedAttributes[foundAttribute.first].get<std::string>());
std::regex expectedRegex(expectedVal);
GTEST_LOG_(INFO) << "expected Regex: " << expectedVal << std::endl;
GTEST_LOG_(INFO) << "actual val: " << actualVal << std::endl;
EXPECT_TRUE(std::regex_match(actualVal, expectedRegex));
break;
}
case opentelemetry::common::kTypeDouble: {
@ -275,7 +287,8 @@ TEST_F(OpenTelemetryServiceTests, SimplestTest)
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan("My API", {});
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
EXPECT_FALSE(contextAndSpan.first.IsCancelled());
}
}
@ -311,7 +324,8 @@ TEST_F(OpenTelemetryServiceTests, CreateWithExplicitProvider)
clientOptions, "my-service", "1.0beta-2");
Azure::Core::Context clientContext;
auto contextAndSpan = serviceTrace.CreateSpan("My API", clientContext);
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, clientContext);
EXPECT_FALSE(contextAndSpan.first.IsCancelled());
}
// Now let's verify what was logged via OpenTelemetry.
@ -321,6 +335,7 @@ TEST_F(OpenTelemetryServiceTests, CreateWithExplicitProvider)
VerifySpan(spans[0], R"(
{
"name": "My API",
"kind": "internal",
"attributes": {
"az.namespace": "my-service"
},
@ -349,7 +364,8 @@ TEST_F(OpenTelemetryServiceTests, CreateWithImplicitProvider)
clientOptions, "my-service", "1.0beta-2");
Azure::Core::Context clientContext;
auto contextAndSpan = serviceTrace.CreateSpan("My API", clientContext);
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, clientContext);
EXPECT_FALSE(contextAndSpan.first.IsCancelled());
}
@ -360,6 +376,7 @@ TEST_F(OpenTelemetryServiceTests, CreateWithImplicitProvider)
VerifySpan(spans[0], R"(
{
"name": "My API",
"kind": "internal",
"attributes": {
"az.namespace": "my-service"
},
@ -383,6 +400,10 @@ TEST_F(OpenTelemetryServiceTests, NestSpans)
Azure::Core::Context::ApplicationContext.SetTracerProvider(provider);
Azure::Core::Http::Request outerRequest(
HttpMethod::Post, Azure::Core::Url("https://www.microsoft.com"));
Azure::Core::Http::Request innerRequest(
HttpMethod::Post, Azure::Core::Url("https://www.microsoft.com"));
{
Azure::Core::_internal::ClientOptions clientOptions;
clientOptions.Telemetry.ApplicationId = "MyApplication";
@ -391,13 +412,17 @@ TEST_F(OpenTelemetryServiceTests, NestSpans)
clientOptions, "my-service", "1.0beta-2");
Azure::Core::Context parentContext;
auto contextAndSpan = serviceTrace.CreateSpan("My API", parentContext);
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Client, parentContext);
EXPECT_FALSE(contextAndSpan.first.IsCancelled());
parentContext = contextAndSpan.first;
contextAndSpan.second.PropagateToHttpHeaders(outerRequest);
{
auto innerContextAndSpan = serviceTrace.CreateSpan("Nested API", parentContext);
auto innerContextAndSpan = serviceTrace.CreateSpan(
"Nested API", Azure::Core::Tracing::_internal::SpanKind::Server, parentContext);
EXPECT_FALSE(innerContextAndSpan.first.IsCancelled());
innerContextAndSpan.second.PropagateToHttpHeaders(innerRequest);
}
}
// Now let's verify what was logged via OpenTelemetry.
@ -431,9 +456,73 @@ TEST_F(OpenTelemetryServiceTests, NestSpans)
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());
// The trace ID for the inner and outer requests must be the same, the parent-id/span-id must be
// different.
//
// Returns a 4 element array.
// Array[0] is the version of the TraceParent header.
// Array[1] is the trace-id of the TraceParent header.
// Array[2] is the parent-id/span-id of the TraceParent header.
// Array[3] is the trace-flags of the TraceParent header.
auto ParseTraceParent = [](const std::string& traceParent) -> std::array<std::string, 4> {
std::array<std::string, 4> returnedComponents;
std::string component;
size_t index = 0;
for (auto ch : traceParent)
{
if (ch != '-')
{
component.push_back(ch);
}
else
{
returnedComponents[index] = component;
component.clear();
index += 1;
}
}
EXPECT_EQ(3ul, index);
returnedComponents[3] = component;
return returnedComponents;
};
auto outerTraceId = ParseTraceParent(outerRequest.GetHeader("traceparent").Value());
auto innerTraceId = ParseTraceParent(innerRequest.GetHeader("traceparent").Value());
// Version should always match.
EXPECT_EQ(outerTraceId[0], innerTraceId[0]);
// Trace ID should always match.
EXPECT_EQ(outerTraceId[1], innerTraceId[1]);
// Span-Id should never match.
EXPECT_NE(outerTraceId[2], innerTraceId[2]);
}
}
namespace {
class NoOpPolicy final : public HttpPolicy {
std::function<std::unique_ptr<RawResponse>(Request&)> m_createResponse{};
public:
std::unique_ptr<HttpPolicy> Clone() const override { return std::make_unique<NoOpPolicy>(*this); }
std::unique_ptr<RawResponse> Send(Request& request, NextHttpPolicy, Azure::Core::Context const&)
const override
{
if (m_createResponse)
{
return m_createResponse(request);
}
else
{
return std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Something");
}
}
NoOpPolicy() = default;
NoOpPolicy(std::function<std::unique_ptr<RawResponse>(Request&)> createResponse)
: HttpPolicy(), m_createResponse(createResponse){};
};
} // namespace
// Create a serviceTrace and span using a provider specified in the ClientOptions.
class ServiceClientOptions : public Azure::Core::_internal::ClientOptions {
public:
@ -443,23 +532,49 @@ public:
class ServiceClient {
private:
ServiceClientOptions m_clientOptions;
Azure::Core::Tracing::_internal::DiagnosticTracingFactory m_serviceTrace;
Azure::Core::Tracing::_internal::DiagnosticTracingFactory m_tracingFactory;
std::unique_ptr<Azure::Core::Http::_internal::HttpPipeline> m_pipeline;
public:
explicit ServiceClient(ServiceClientOptions const& clientOptions = ServiceClientOptions{})
: m_serviceTrace(clientOptions, "Azure.Core.OpenTelemetry.Test.Service", "1.0.0.beta-2")
: m_tracingFactory(clientOptions, "Azure.Core.OpenTelemetry.Test.Service", "1.0.0.beta-2")
{
std::vector<std::unique_ptr<HttpPolicy>> policies;
policies.emplace_back(std::make_unique<TelemetryPolicy>(
"Azure.Core.OpenTelemetry.Test.Service", "1.0.0.beta-2", clientOptions.Telemetry));
policies.emplace_back(std::make_unique<RequestIdPolicy>());
policies.emplace_back(std::make_unique<RetryPolicy>(RetryOptions{}));
// Add the request ID policy - this adds the x-ms-request-id attribute to the pipeline.
policies.emplace_back(
std::make_unique<RequestActivityPolicy>(Azure::Core::_internal::InputSanitizer{}));
// Final policy - functions as the HTTP transport policy.
policies.emplace_back(std::make_unique<NoOpPolicy>([&](Request& request) {
// If the request is for port 12345, throw an exception.
if (request.GetUrl().GetPort() == 12345)
{
throw Azure::Core::RequestFailedException("it all goes wrong here.");
}
return std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Something");
}));
m_pipeline = std::make_unique<Azure::Core::Http::_internal::HttpPipeline>(policies);
}
Azure::Response<std::string> GetConfigurationString(
std::string const& inputString,
Azure::Core::Context const& context = Azure::Core::Context{})
{
auto contextAndSpan = m_serviceTrace.CreateSpan("GetConfigurationString", context);
auto contextAndSpan = m_tracingFactory.CreateSpan(
"GetConfigurationString", Azure::Core::Tracing::_internal::SpanKind::Internal, context);
// <Call Into Service via an HTTP pipeline>
Azure::Core::Http::Request requestToSend(
HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com/"));
std::unique_ptr<Azure::Core::Http::RawResponse> response
= SendHttpRequest(false, contextAndSpan.first);
= m_pipeline->Send(requestToSend, contextAndSpan.first);
// Reflect that the operation was successful.
contextAndSpan.second.SetStatus(Azure::Core::Tracing::_internal::SpanStatus::Ok);
@ -468,47 +583,22 @@ public:
// When contextAndSpan.second goes out of scope, it ends the span, which will record it.
}
std::unique_ptr<Azure::Core::Http::RawResponse> ActuallySendHttpRequest(
Azure::Core::Context const& context)
{
auto contextAndSpan
= Azure::Core::Tracing::_internal::DiagnosticTracingFactory::CreateSpanFromContext(
"HTTP GET#2", context);
return std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK");
}
std::unique_ptr<Azure::Core::Http::RawResponse> 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<Azure::Core::Http::RawResponse> response
= ActuallySendHttpRequest(contextAndSpan.first);
return std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK");
}
Azure::Response<std::string> ApiWhichThrows(
std::string const&,
Azure::Core::Context const& context = Azure::Core::Context{})
{
auto contextAndSpan = m_serviceTrace.CreateSpan("ApiWhichThrows", context);
auto contextAndSpan = m_tracingFactory.CreateSpan(
"ApiWhichThrows", Azure::Core::Tracing::_internal::SpanKind::Internal, context);
try
{
auto rawResponse = SendHttpRequest(false, contextAndSpan.first);
return Azure::Response<std::string>("", std::move(rawResponse));
// <Call Into Service via an HTTP pipeline>
Azure::Core::Http::Request requestToSend(
HttpMethod::Get, Azure::Core::Url("https://www.microsoft.com/:12345/index.html"));
std::unique_ptr<Azure::Core::Http::RawResponse> response
= m_pipeline->Send(requestToSend, contextAndSpan.first);
return Azure::Response<std::string>("", std::move(response));
}
catch (std::exception const& ex)
{
@ -541,15 +631,20 @@ TEST_F(OpenTelemetryServiceTests, ServiceApiImplementation)
}
// Now let's verify what was logged via OpenTelemetry.
auto spans = m_spanData->GetSpans();
EXPECT_EQ(3ul, spans.size());
EXPECT_EQ(2ul, spans.size());
VerifySpan(spans[0], R"(
{
"name": "HTTP GET#2",
"kind": "internal",
"name": "HTTP GET #0",
"kind": "client",
"statusCode": "unset",
"attributes": {
"az.namespace": "Azure.Core.OpenTelemetry.Test.Service"
"az.namespace": "Azure.Core.OpenTelemetry.Test.Service",
"http.method": "GET",
"http.url": "https://www.microsoft.com",
"requestId": ".*",
"http.user_agent": "MyApplication azsdk-cpp-Azure.Core.OpenTelemetry.Test.Service/1.0.0.beta-2.*",
"http.status_code": "200"
},
"library": {
"name": "Azure.Core.OpenTelemetry.Test.Service",
@ -558,20 +653,6 @@ TEST_F(OpenTelemetryServiceTests, ServiceApiImplementation)
})");
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",

View File

@ -3,7 +3,8 @@
## 1.7.0-beta.1 (Unreleased)
### Features Added
Added implementation for Distributed Tracing.
- Added prototypes and initial service support for Distributed Tracing.
### Breaking Changes

View File

@ -38,10 +38,10 @@ endif()
if(BUILD_TRANSPORT_CURL)
SET(CURL_TRANSPORT_ADAPTER_SRC
src/http/curl/curl.cpp
src/http/curl/curl_connection_pool_private.hpp
src/http/curl/curl_connection_private.hpp
src/http/curl/curl_session_private.hpp
src/http/curl/curl.cpp
)
SET(CURL_TRANSPORT_ADAPTER_INC
inc/azure/core/http/curl_transport.hpp
@ -56,37 +56,39 @@ set(
AZURE_CORE_HEADER
${CURL_TRANSPORT_ADAPTER_INC}
${WIN_TRANSPORT_ADAPTER_INC}
inc/azure/core.hpp
inc/azure/core/azure_assert.hpp
inc/azure/core/base64.hpp
inc/azure/core/case_insensitive_containers.hpp
inc/azure/core/context.hpp
inc/azure/core/credentials/credentials.hpp
inc/azure/core/credentials/token_credential_options.hpp
inc/azure/core/cryptography/hash.hpp
inc/azure/core/datetime.hpp
inc/azure/core/diagnostics/logger.hpp
inc/azure/core/http/policies/policy.hpp
inc/azure/core/dll_import_export.hpp
inc/azure/core/etag.hpp
inc/azure/core/exception.hpp
inc/azure/core/http/http.hpp
inc/azure/core/http/http_status_code.hpp
inc/azure/core/http/policies/policy.hpp
inc/azure/core/http/raw_response.hpp
inc/azure/core/http/transport.hpp
inc/azure/core/internal/client_options.hpp
inc/azure/core/internal/contract.hpp
inc/azure/core/internal/cryptography/sha_hash.hpp
inc/azure/core/internal/diagnostics/log.hpp
inc/azure/core/internal/environment.hpp
inc/azure/core/internal/extendable_enumeration.hpp
inc/azure/core/internal/http/pipeline.hpp
inc/azure/core/internal/io/null_body_stream.hpp
inc/azure/core/internal/json/json.hpp
inc/azure/core/internal/json/json_optional.hpp
inc/azure/core/internal/json/json_serializable.hpp
inc/azure/core/internal/client_options.hpp
inc/azure/core/internal/contract.hpp
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/internal/input_sanitizer.hpp
inc/azure/core/io/body_stream.hpp
inc/azure/core/azure_assert.hpp
inc/azure/core/base64.hpp
inc/azure/core/case_insensitive_containers.hpp
inc/azure/core/context.hpp
inc/azure/core/datetime.hpp
inc/azure/core/dll_import_export.hpp
inc/azure/core/etag.hpp
inc/azure/core/exception.hpp
inc/azure/core/match_conditions.hpp
inc/azure/core/modified_conditions.hpp
inc/azure/core/nullable.hpp
@ -99,41 +101,44 @@ set(
inc/azure/core/tracing/tracing.hpp
inc/azure/core/url.hpp
inc/azure/core/uuid.hpp
inc/azure/core.hpp)
)
set(
AZURE_CORE_SOURCE
${CURL_TRANSPORT_ADAPTER_SRC}
${WIN_TRANSPORT_ADAPTER_SRC}
src/azure_assert.cpp
src/base64.cpp
src/context.cpp
src/cryptography/md5.cpp
src/cryptography/sha_hash.cpp
src/datetime.cpp
src/environment.cpp
src/environment_log_level_listener.cpp
src/etag.cpp
src/exception.cpp
src/http/bearer_token_authentication_policy.cpp
src/http/http.cpp
src/http/log_policy.cpp
src/http/policy.cpp
src/http/raw_response.cpp
src/http/request.cpp
src/http/request_activity_policy.cpp
src/http/retry_policy.cpp
src/http/telemetry_policy.cpp
src/http/transport_policy.cpp
src/http/url.cpp
src/io/body_stream.cpp
src/io/random_access_file_body_stream.cpp
src/private/environment_log_level_listener.hpp
src/private/package_version.hpp
src/azure_assert.cpp
src/base64.cpp
src/context.cpp
src/datetime.cpp
src/environment.cpp
src/environment_log_level_listener.cpp
src/etag.cpp
src/exception.cpp
src/logger.cpp
src/operation_status.cpp
src/private/environment_log_level_listener.hpp
src/private/package_version.hpp
src/private/input_sanitizer.cpp
src/strings.cpp
src/tracing/tracing.cpp
src/uuid.cpp
src/tracing/tracing.cpp)
)
add_library(azure-core ${AZURE_CORE_HEADER} ${AZURE_CORE_SOURCE})

View File

@ -12,7 +12,6 @@
#include "azure/core/datetime.hpp"
#include "azure/core/dll_import_export.hpp"
#include "azure/core/rtti.hpp"
#include "azure/core/tracing/tracing.hpp"
#include <atomic>
#include <chrono>
#include <memory>
@ -20,6 +19,11 @@
#include <string>
#include <type_traits>
// Forward declare TracerProvider to resolve an include file dependency ordering problem.
namespace Azure { namespace Core { namespace Tracing {
class TracerProvider;
}}} // namespace Azure::Core::Tracing
namespace Azure { namespace Core {
/**

View File

@ -273,6 +273,16 @@ namespace Azure { namespace Core { namespace Http {
*/
void SetHeader(std::string const& name, std::string const& value);
/**
* @brief Gets a specific HTTP header from an #Azure::Core::Http::Request.
*
* @param name The name for the header to be retrieved.
* @return The desired header, or an empty nullable if it is not found..
*
* @throw if \p name is an invalid header key.
*/
Azure::Nullable<std::string> GetHeader(std::string const& name);
/**
* @brief Remove an HTTP header.
*
@ -285,11 +295,13 @@ namespace Azure { namespace Core { namespace Http {
* @brief Get HttpMethod.
*
*/
HttpMethod GetMethod() const;
HttpMethod const& GetMethod() const;
/**
* @brief Get HTTP headers.
*
* @remark Note that this function return a COPY of the headers for this request.
*
*/
CaseInsensitiveMap GetHeaders() const;

View File

@ -14,9 +14,11 @@
#include "azure/core/dll_import_export.hpp"
#include "azure/core/http/http.hpp"
#include "azure/core/http/transport.hpp"
#include "azure/core/internal/input_sanitizer.hpp"
#include "azure/core/tracing/tracing.hpp"
#include "azure/core/uuid.hpp"
#include <atomic>
#include <chrono>
#include <cstddef>
#include <map>
@ -382,6 +384,44 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
}
};
/**
* @brief HTTP Request Activity policy.
*
* @details Registers an HTTP request with the distributed tracing infrastructure, adding
* the traceparent header to the request if necessary.
*
* This policy is intended to be inserted into the HTTP pipeline *after* the retry policy.
*/
class RequestActivityPolicy final : public HttpPolicy {
private:
Azure::Core::_internal::InputSanitizer m_inputSanitizer;
public:
/**
* @brief Constructs HTTP Request Activity policy.
*/
// explicit RequestActivityPolicy() = default;
/**
* @brief Constructs HTTP Request Activity policy.
*
* @param inputSanitizer for sanitizing data before it is logged.
*/
explicit RequestActivityPolicy(Azure::Core::_internal::InputSanitizer const& inputSanitizer)
: m_inputSanitizer(inputSanitizer)
{
}
std::unique_ptr<HttpPolicy> Clone() const override
{
return std::make_unique<RequestActivityPolicy>(*this);
}
std::unique_ptr<RawResponse> Send(
Request& request,
NextHttpPolicy nextPolicy,
Context const& context) const override;
};
/**
* @brief HTTP telemetry policy.
*
@ -475,13 +515,18 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
*/
class LogPolicy final : public HttpPolicy {
LogOptions m_options;
Azure::Core::_internal::InputSanitizer m_inputSanitizer;
public:
/**
* @brief Constructs HTTP logging policy.
*
*/
explicit LogPolicy(LogOptions options) : m_options(std::move(options)) {}
explicit LogPolicy(LogOptions options)
: m_options(std::move(options)),
m_inputSanitizer(m_options.AllowedHttpQueryParameters, m_options.AllowedHttpHeaders)
{
}
std::unique_ptr<HttpPolicy> Clone() const override
{

View File

@ -14,6 +14,7 @@
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/http/transport.hpp"
#include "azure/core/internal/client_options.hpp"
#include "azure/core/internal/input_sanitizer.hpp"
#include <memory>
#include <vector>
@ -76,16 +77,20 @@ namespace Azure { namespace Core { namespace Http { namespace _internal {
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>>&& perRetryPolicies,
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>>&& perCallPolicies)
{
Azure::Core::_internal::InputSanitizer inputSanitizer(
clientOptions.Log.AllowedHttpQueryParameters, clientOptions.Log.AllowedHttpHeaders);
auto const& perCallClientPolicies = clientOptions.PerOperationPolicies;
auto const& perRetryClientPolicies = clientOptions.PerRetryPolicies;
// Adding 5 for:
// Adding 6 for:
// - TelemetryPolicy
// - RequestIdPolicy
// - RetryPolicy
// - LogPolicy
// - RequestActivityPolicy
// - TransportPolicy
auto pipelineSize = perCallClientPolicies.size() + perRetryClientPolicies.size()
+ perRetryPolicies.size() + perCallPolicies.size() + 5;
+ perRetryPolicies.size() + perCallPolicies.size() + 6;
m_policies.reserve(pipelineSize);
@ -98,6 +103,7 @@ namespace Azure { namespace Core { namespace Http { namespace _internal {
// Request Id
m_policies.emplace_back(
std::make_unique<Azure::Core::Http::Policies::_internal::RequestIdPolicy>());
// Telemetry
m_policies.emplace_back(
std::make_unique<Azure::Core::Http::Policies::_internal::TelemetryPolicy>(
@ -124,6 +130,11 @@ namespace Azure { namespace Core { namespace Http { namespace _internal {
m_policies.emplace_back(policy->Clone());
}
// Add a request activity policy which will generate distributed traces for the pipeline.
m_policies.emplace_back(
std::make_unique<Azure::Core::Http::Policies::_internal::RequestActivityPolicy>(
inputSanitizer));
// logging - won't update request
m_policies.emplace_back(
std::make_unique<Azure::Core::Http::Policies::_internal::LogPolicy>(clientOptions.Log));

View File

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include "azure/core/url.hpp"
#include <string>
namespace Azure { namespace Core { namespace _internal {
class InputSanitizer final {
/**
* @brief HTTP header names that are allowed to be logged.
*/
Azure::Core::CaseInsensitiveSet m_allowedHttpHeaders;
/**
* @brief HTTP query parameter names that are allowed to be logged.
*/
std::set<std::string> m_allowedHttpQueryParameters;
// Manifest constant indicating a field was redacted.
static const char* m_RedactedPlaceholder;
public:
InputSanitizer() = default;
InputSanitizer(
std::set<std::string> const& allowedHttpQueryParameters,
Azure::Core::CaseInsensitiveSet const& allowedHttpHeaders)
: m_allowedHttpHeaders(allowedHttpHeaders),
m_allowedHttpQueryParameters(allowedHttpQueryParameters)
{
}
/**
* @brief Sanitizes the specified URL according to the sanitization rules configured.
*
* @param url Url to sanitize. Specified elements will be redacted from the URL.
* @return sanitized URL.
*/
Azure::Core::Url SanitizeUrl(Url const& url) const;
/**
* @brief Sanitizes the provided HTTP header value according to the sanitization rules
* configured.
*
* @param headerName Name of the header to sanitize.
* @param headerValue Current value of the header to sanitize.
* @return Sanitized header value.
*/
std::string SanitizeHeader(std::string const& headerName, std::string const& headerValue) const;
};
}}} // namespace Azure::Core::_internal

View File

@ -65,6 +65,7 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
m_span->SetStatus(status, description);
}
}
/**
* @brief Adds a set of attributes to the span.
*
@ -78,6 +79,21 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
}
}
/**
* @brief Adds a single attributes to the span.
*
* @param attributeName Name of the attribute to be added.
* @param attributeValue Value of the attribute to be added.
*/
virtual void AddAttribute(std::string const& attributeName, std::string const& attributeValue)
override
{
if (m_span)
{
m_span->AddAttribute(attributeName, attributeValue);
}
}
/**
* @brief Adds an event to the span.
*
@ -123,6 +139,20 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
m_span->AddEvent(exception);
}
}
/**
* @brief Propogate information from the current span to the HTTP request headers.
*
* @param request HTTP Request to the service. If there is an active tracing span, this will
* add required headers to the HTTP Request.
*/
virtual void PropagateToHttpHeaders(Azure::Core::Http::Request& request) override
{
if (m_span)
{
m_span->PropagateToHttpHeaders(request);
}
}
};
/**
@ -178,10 +208,12 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
ContextAndSpan CreateSpan(
std::string const& spanName,
Azure::Core::Tracing::_internal::SpanKind const& spanKind,
Azure::Core::Context const& clientContext);
static ContextAndSpan CreateSpanFromContext(
std::string const& spanName,
Azure::Core::Tracing::_internal::SpanKind const& spanKind,
Azure::Core::Context const& clientContext);
std::unique_ptr<Azure::Core::Tracing::_internal::AttributeSet> CreateAttributeSet();

View File

@ -162,8 +162,8 @@ namespace Azure { namespace Core { namespace IO {
namespace _internal {
/**
* @brief A concrete implementation of #Azure::Core::IO::BodyStream used for reading data from
* a file from any offset and length within it.
* @brief A concrete implementation of #Azure::Core::IO::BodyStream used for reading data
* from a file from any offset and length within it.
*/
class RandomAccessFileBodyStream final : public BodyStream {
private:
@ -284,8 +284,8 @@ namespace Azure { namespace Core { namespace IO {
};
/**
* @brief A concrete implementation of #Azure::Core::IO::BodyStream that wraps another stream and
* reports progress
* @brief A concrete implementation of #Azure::Core::IO::BodyStream that wraps another stream
* and reports progress
*/
class ProgressBodyStream : public BodyStream {
private:

View File

@ -17,6 +17,11 @@
#include <string>
#include <vector>
// Forward declare Azure::Core::Http::Request to resolve an include file ordering problem.
namespace Azure { namespace Core { namespace Http {
class Request;
}}} // namespace Azure::Core::Http
namespace Azure { namespace Core { namespace Tracing {
namespace _internal {
@ -104,10 +109,10 @@ namespace Azure { namespace Core { namespace Tracing {
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.
* @brief destroys an AttributeSet - virtual destructor to enable base class users to
* destroy derived classes.
*/
virtual ~AttributeSet(){};
virtual ~AttributeSet() = default;
};
/** @brief The Type of Span.
@ -184,6 +189,15 @@ namespace Azure { namespace Core { namespace Tracing {
*/
virtual void AddAttributes(AttributeSet const& attributeToAdd) = 0;
/**
* @brief Adds a single string valued attribute to the span.
*
* @param attributeName Name of the attribute to add.
* @param attributeValue value of the attribute.
*/
virtual void AddAttribute(std::string const& attributeName, std::string const& attributeValue)
= 0;
/**
* @brief Adds an event to the span.
*
@ -217,6 +231,14 @@ namespace Azure { namespace Core { namespace Tracing {
* @param description A description associated with the Status.
*/
virtual void SetStatus(SpanStatus const& status, std::string const& description = "") = 0;
/**
* @brief Propogate information from the current span to the HTTP request headers.
*
* @param request HTTP Request to the service. If there is an active tracing span, this will
* add required headers to the HTTP Request.
*/
virtual void PropagateToHttpHeaders(Azure::Core::Http::Request& request) = 0;
};
/**

View File

@ -21,8 +21,8 @@ std::string RedactedPlaceholder = "REDACTED";
inline void AppendHeaders(
std::ostringstream& log,
Azure::Core::CaseInsensitiveMap const& headers,
Azure::Core::CaseInsensitiveSet const& allowedHaders)
Azure::Core::_internal::InputSanitizer const& inputSanitizer,
Azure::Core::CaseInsensitiveMap const& headers)
{
for (auto const& header : headers)
{
@ -30,90 +30,27 @@ inline void AppendHeaders(
if (!header.second.empty())
{
log
<< ((allowedHaders.find(header.first) != allowedHaders.end()) ? header.second
: RedactedPlaceholder);
log << inputSanitizer.SanitizeHeader(header.first, header.second);
}
}
}
inline void LogUrlWithoutQuery(std::ostringstream& log, Url const& url)
inline std::string GetRequestLogMessage(
Azure::Core::_internal::InputSanitizer const& inputSanitizer,
Request const& request)
{
if (!url.GetScheme().empty())
{
log << url.GetScheme() << "://";
}
log << url.GetHost();
if (url.GetPort() != 0)
{
log << ":" << url.GetPort();
}
if (!url.GetPath().empty())
{
log << "/" << url.GetPath();
}
}
inline std::string GetRequestLogMessage(LogOptions const& options, Request const& request)
{
auto const& requestUrl = request.GetUrl();
std::ostringstream log;
log << "HTTP Request : " << request.GetMethod().ToString() << " ";
LogUrlWithoutQuery(log, requestUrl);
{
auto encodedRequestQueryParams = requestUrl.GetQueryParameters();
Azure::Core::Url urlToLog(inputSanitizer.SanitizeUrl(request.GetUrl()));
log << urlToLog.GetAbsoluteUrl();
std::remove_const<std::remove_reference<decltype(encodedRequestQueryParams)>::type>::type
loggedQueryParams;
if (!encodedRequestQueryParams.empty())
{
auto const& unencodedAllowedQueryParams = options.AllowedHttpQueryParameters;
if (!unencodedAllowedQueryParams.empty())
{
std::remove_const<std::remove_reference<decltype(unencodedAllowedQueryParams)>::type>::type
encodedAllowedQueryParams;
std::transform(
unencodedAllowedQueryParams.begin(),
unencodedAllowedQueryParams.end(),
std::inserter(encodedAllowedQueryParams, encodedAllowedQueryParams.begin()),
[](std::string const& s) { return Url::Encode(s); });
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
{
if (encodedRequestQueryParam.second.empty()
|| (encodedAllowedQueryParams.find(encodedRequestQueryParam.first)
!= encodedAllowedQueryParams.end()))
{
loggedQueryParams.insert(encodedRequestQueryParam);
}
else
{
loggedQueryParams.insert(
std::make_pair(encodedRequestQueryParam.first, RedactedPlaceholder));
}
}
}
else
{
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
{
loggedQueryParams.insert(
std::make_pair(encodedRequestQueryParam.first, RedactedPlaceholder));
}
}
log << Azure::Core::_detail::FormatEncodedUrlQueryParameters(loggedQueryParams);
}
}
AppendHeaders(log, request.GetHeaders(), options.AllowedHttpHeaders);
AppendHeaders(log, inputSanitizer, request.GetHeaders());
return log.str();
}
inline std::string GetResponseLogMessage(
LogOptions const& options,
Azure::Core::_internal::InputSanitizer const& inputSanitizer,
RawResponse const& response,
std::chrono::system_clock::duration const& duration)
{
@ -124,36 +61,39 @@ inline std::string GetResponseLogMessage(
<< "ms) : " << static_cast<int>(response.GetStatusCode()) << " "
<< response.GetReasonPhrase();
AppendHeaders(log, response.GetHeaders(), options.AllowedHttpHeaders);
AppendHeaders(log, inputSanitizer, response.GetHeaders());
return log.str();
}
} // namespace
Azure::Core::CaseInsensitiveSet const
Azure::Core::Http::Policies::_detail::g_defaultAllowedHttpHeaders
= {"x-ms-request-id",
"x-ms-client-request-id",
"x-ms-return-client-request-id",
"traceparent",
"Accept",
"Cache-Control",
"Connection",
"Content-Length",
"Content-Type",
"Date",
"ETag",
"Expires",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Unmodified-Since",
"Last-Modified",
"Pragma",
"Request-Id",
"Retry-After",
"Server",
"Transfer-Encoding",
"User-Agent"};
= {
"Accept",
"Cache-Control",
"Connection",
"Content-Length",
"Content-Type",
"Date",
"ETag",
"Expires",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Unmodified-Since",
"Last-Modified",
"Pragma",
"Request-Id",
"Retry-After",
"Server",
"traceparent",
"tracestate",
"Transfer-Encoding",
"User-Agent"
"x-ms-client-request-id",
"x-ms-request-id",
"x-ms-return-client-request-id",
};
std::unique_ptr<RawResponse> LogPolicy::Send(
Request& request,
@ -165,7 +105,7 @@ std::unique_ptr<RawResponse> LogPolicy::Send(
if (Log::ShouldWrite(Logger::Level::Verbose))
{
Log::Write(Logger::Level::Informational, GetRequestLogMessage(m_options, request));
Log::Write(Logger::Level::Informational, GetRequestLogMessage(m_inputSanitizer, request));
}
else
{
@ -177,7 +117,8 @@ std::unique_ptr<RawResponse> LogPolicy::Send(
auto const end = std::chrono::system_clock::now();
Log::Write(
Logger::Level::Informational, GetResponseLogMessage(m_options, *response, end - start));
Logger::Level::Informational,
GetResponseLogMessage(m_inputSanitizer, *response, end - start));
return response;
}

View File

@ -22,6 +22,24 @@ static Azure::Core::CaseInsensitiveMap MergeMaps(
}
} // namespace
Azure::Nullable<std::string> Request::GetHeader(std::string const& name)
{
std::vector<std::string> returnedHeaders;
auto headerNameLowerCase = Azure::Core::_internal::StringExtensions::ToLower(name);
auto retryHeader = this->m_retryHeaders.find(headerNameLowerCase);
if (retryHeader != this->m_retryHeaders.end())
{
return retryHeader->second;
}
auto header = this->m_headers.find(headerNameLowerCase);
if (header != this->m_headers.end())
{
return header->second;
}
return Azure::Nullable<std::string>{};
}
void Request::SetHeader(std::string const& name, std::string const& value)
{
auto headerNameLowerCase = Azure::Core::_internal::StringExtensions::ToLower(name);
@ -50,7 +68,7 @@ void Request::StartTry()
}
}
HttpMethod Request::GetMethod() const { return this->m_method; }
HttpMethod const& Request::GetMethod() const { return this->m_method; }
Azure::Core::CaseInsensitiveMap Request::GetHeaders() const
{

View File

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/internal/diagnostics/log.hpp"
#include "azure/core/internal/input_sanitizer.hpp"
#include "azure/core/internal/tracing/service_tracing.hpp"
#include <algorithm>
#include <cstdlib>
#include <limits>
#include <sstream>
#include <thread>
using Azure::Core::Context;
using namespace Azure::Core::Http;
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http::Policies::_internal;
using namespace Azure::Core::Tracing::_internal;
std::unique_ptr<RawResponse> RequestActivityPolicy::Send(
Request& request,
NextHttpPolicy nextPolicy,
Context const& context) const
{
// Create a tracing span over the HTTP request.
std::stringstream ss;
// We know that the retry policy MUST be above us in the hierarchy, so ask it for the current
// retry count.
auto retryCount = RetryPolicy::GetRetryCount(context);
if (retryCount == -1)
{
// We don't have a RetryPolicy in the policy stack - just assume this is request 0.
retryCount = 0;
}
ss << "HTTP " << request.GetMethod().ToString() << " #" << retryCount;
auto contextAndSpan
= Azure::Core::Tracing::_internal::DiagnosticTracingFactory::CreateSpanFromContext(
ss.str(), SpanKind::Client, context);
auto scope = std::move(contextAndSpan.second);
scope.AddAttribute(TracingAttributes::HttpMethod.ToString(), request.GetMethod().ToString());
scope.AddAttribute("http.url", m_inputSanitizer.SanitizeUrl(request.GetUrl()).GetAbsoluteUrl());
{
Azure::Nullable<std::string> requestId = request.GetHeader("x-ms-client-request-id");
if (requestId.HasValue())
{
scope.AddAttribute(TracingAttributes::RequestId.ToString(), requestId.Value());
}
}
{
auto userAgent = request.GetHeader("User-Agent");
if (userAgent.HasValue())
{
scope.AddAttribute(TracingAttributes::HttpUserAgent.ToString(), userAgent.Value());
}
}
// Propagate information from the scope to the HTTP headers.
//
// This will add the "traceparent" header and any other OpenTelemetry related headers.
scope.PropagateToHttpHeaders(request);
try
{
// Send the request on to the service.
auto response = nextPolicy.Send(request, contextAndSpan.first);
// And register the headers we received from the service.
scope.AddAttribute(
TracingAttributes::HttpStatusCode.ToString(),
std::to_string(static_cast<int>(response->GetStatusCode())));
auto const& responseHeaders = response->GetHeaders();
auto serviceRequestId = responseHeaders.find("x-ms-request-id");
if (serviceRequestId != responseHeaders.end())
{
scope.AddAttribute(TracingAttributes::ServiceRequestId.ToString(), serviceRequestId->second);
}
return response;
}
catch (const TransportException& e)
{
scope.AddEvent(e);
// Rethrow the exception.
throw;
}
}

View File

@ -0,0 +1,89 @@
#include "azure/core/internal/input_sanitizer.hpp"
#include "azure/core/url.hpp"
#include <regex>
#include <sstream>
namespace Azure { namespace Core { namespace _internal {
const char* InputSanitizer::m_RedactedPlaceholder = "REDACTED";
Azure::Core::Url InputSanitizer::SanitizeUrl(Azure::Core::Url const& url) const
{
std::ostringstream ss;
// Sanitize the non-query part of the URL (remove username and password).
if (!url.GetScheme().empty())
{
ss << url.GetScheme() << "://";
}
ss << url.GetHost();
if (url.GetPort() != 0)
{
ss << ":" << url.GetPort();
}
if (!url.GetPath().empty())
{
ss << "/" << url.GetPath();
}
{
auto encodedRequestQueryParams = url.GetQueryParameters();
std::remove_const<std::remove_reference<decltype(encodedRequestQueryParams)>::type>::type
loggedQueryParams;
if (!encodedRequestQueryParams.empty())
{
auto const& unencodedAllowedQueryParams = m_allowedHttpQueryParameters;
if (!unencodedAllowedQueryParams.empty())
{
std::remove_const<std::remove_reference<decltype(unencodedAllowedQueryParams)>::type>::
type encodedAllowedQueryParams;
std::transform(
unencodedAllowedQueryParams.begin(),
unencodedAllowedQueryParams.end(),
std::inserter(encodedAllowedQueryParams, encodedAllowedQueryParams.begin()),
[](std::string const& s) { return Url::Encode(s); });
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
{
if (encodedRequestQueryParam.second.empty()
|| (encodedAllowedQueryParams.find(encodedRequestQueryParam.first)
!= encodedAllowedQueryParams.end()))
{
loggedQueryParams.insert(encodedRequestQueryParam);
}
else
{
loggedQueryParams.insert(
std::make_pair(encodedRequestQueryParam.first, m_RedactedPlaceholder));
}
}
}
else
{
for (auto const& encodedRequestQueryParam : encodedRequestQueryParams)
{
loggedQueryParams.insert(
std::make_pair(encodedRequestQueryParam.first, m_RedactedPlaceholder));
}
}
ss << Azure::Core::_detail::FormatEncodedUrlQueryParameters(loggedQueryParams);
}
}
return Azure::Core::Url(ss.str());
}
std::string InputSanitizer::SanitizeHeader(std::string const& header, std::string const& value)
const
{
if (m_allowedHttpHeaders.find(header) != m_allowedHttpHeaders.end())
{
return value;
}
return m_RedactedPlaceholder;
}
}}} // namespace Azure::Core::_internal

View File

@ -14,9 +14,16 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
const SpanStatus SpanStatus::Error("Error");
const TracingAttributes TracingAttributes::AzNamespace("az.namespace");
const TracingAttributes TracingAttributes::ServiceRequestId("serviceRequestId");
const TracingAttributes TracingAttributes::HttpUserAgent("http.user_agent");
const TracingAttributes TracingAttributes::HttpMethod("http.method");
const TracingAttributes TracingAttributes::HttpUrl("http.url");
const TracingAttributes TracingAttributes::RequestId("requestId");
const TracingAttributes TracingAttributes::HttpStatusCode("http.status_code");
DiagnosticTracingFactory::ContextAndSpan DiagnosticTracingFactory::CreateSpan(
std::string const& methodName,
Azure::Core::Tracing::_internal::SpanKind const& spanKind,
Azure::Core::Context const& context)
{
CreateSpanOptions createOptions;
@ -47,6 +54,8 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
createOptions.Attributes->AddAttribute(
TracingAttributes::AzNamespace.ToString(), m_serviceName);
createOptions.Kind = spanKind;
std::shared_ptr<Span> newSpan(m_serviceTracer->CreateSpan(methodName, createOptions));
TracingContext tracingContext = newSpan;
Azure::Core::Context newContext = contextToUse.WithValue(ContextSpanKey, tracingContext);
@ -61,13 +70,14 @@ namespace Azure { namespace Core { namespace Tracing { namespace _internal {
}
DiagnosticTracingFactory::ContextAndSpan DiagnosticTracingFactory::CreateSpanFromContext(
std::string const& spanName,
Azure::Core::Tracing::_internal::SpanKind const& spanKind,
Azure::Core::Context const& context)
{
DiagnosticTracingFactory* tracingFactory
= DiagnosticTracingFactory::DiagnosticFactoryFromContext(context);
if (tracingFactory)
{
return tracingFactory->CreateSpan(spanName, context);
return tracingFactory->CreateSpan(spanName, spanKind, context);
}
else
{

View File

@ -66,6 +66,7 @@ add_executable (
operation_status_test.cpp
pipeline_test.cpp
policy_test.cpp
request_activity_policy_test.cpp
request_id_policy_test.cpp
response_t_test.cpp
retry_policy_test.cpp

View File

@ -0,0 +1,264 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/core/http/policies/policy.hpp"
#include "azure/core/internal/http/pipeline.hpp"
#include "azure/core/internal/tracing/service_tracing.hpp"
#include "azure/core/tracing/tracing.hpp"
#include <gtest/gtest.h>
#include <list>
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace Azure::Core::Http::Policies;
using namespace Azure::Core::Http::Policies::_internal;
using namespace Azure::Core::Tracing::_internal;
using namespace Azure::Core::Tracing;
namespace {
class NoOpPolicy final : public HttpPolicy {
std::function<std::unique_ptr<RawResponse>(Request&)> m_createResponse{};
public:
std::unique_ptr<HttpPolicy> Clone() const override { return std::make_unique<NoOpPolicy>(*this); }
std::unique_ptr<RawResponse> Send(Request& request, NextHttpPolicy, Azure::Core::Context const&)
const override
{
if (m_createResponse)
{
return m_createResponse(request);
}
else
{
return std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Something");
}
}
NoOpPolicy() = default;
NoOpPolicy(std::function<std::unique_ptr<RawResponse>(Request&)> createResponse)
: HttpPolicy(), m_createResponse(createResponse){};
};
// Dummy service tracing class.
class TestSpan final : public Azure::Core::Tracing::_internal::Span {
std::vector<std::string> m_events;
std::map<std::string, std::string> m_stringAttributes;
std::string m_spanName;
public:
TestSpan(std::string const& spanName)
: Azure::Core::Tracing::_internal::Span(), m_spanName(spanName)
{
}
// Inherited via Span
virtual void AddAttributes(AttributeSet const&) override {}
virtual void AddAttribute(std::string const& attributeName, std::string const& attributeValue)
override
{
m_stringAttributes.emplace(std::make_pair(attributeName, attributeValue));
}
virtual void AddEvent(std::string const& eventName, AttributeSet const&) override
{
m_events.push_back(eventName);
}
virtual void AddEvent(std::string const& eventName) override { m_events.push_back(eventName); }
virtual void AddEvent(std::exception const& ex) override { m_events.push_back(ex.what()); }
virtual void SetStatus(SpanStatus const&, std::string const&) override {}
// Inherited via Span
virtual void End(Azure::Nullable<Azure::DateTime>) override {}
// Inherited via Span
virtual void PropagateToHttpHeaders(Azure::Core::Http::Request&) override {}
std::string const& GetName() { return m_spanName; }
std::vector<std::string> const& GetEvents() { return m_events; }
std::map<std::string, std::string> const& GetAttributes() { return m_stringAttributes; }
};
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 {
mutable std::vector<std::shared_ptr<TestSpan>> m_spans;
public:
TestTracer(std::string const&, std::string const&) : Azure::Core::Tracing::_internal::Tracer() {}
std::shared_ptr<Span> CreateSpan(std::string const& spanName, CreateSpanOptions const&)
const override
{
auto returnSpan(std::make_shared<TestSpan>(spanName));
m_spans.push_back(returnSpan);
return returnSpan;
}
std::unique_ptr<AttributeSet> CreateAttributeSet() const override
{
return std::make_unique<TestAttributeSet>();
};
std::vector<std::shared_ptr<TestSpan>> const& GetSpans() { return m_spans; }
};
class TestTracingProvider final : public Azure::Core::Tracing::TracerProvider {
mutable std::list<std::shared_ptr<TestTracer>> m_tracers;
public:
TestTracingProvider() : TracerProvider() {}
~TestTracingProvider() {}
std::shared_ptr<Azure::Core::Tracing::_internal::Tracer> CreateTracer(
std::string const& serviceName,
std::string const& serviceVersion) const override
{
auto returnTracer = std::make_shared<TestTracer>(serviceName, serviceVersion);
m_tracers.push_back(returnTracer);
return returnTracer;
};
std::list<std::shared_ptr<TestTracer>> const& GetTracers() { return m_tracers; }
};
} // namespace
TEST(RequestActivityPolicy, Basic)
{
{
auto testTracer = std::make_shared<TestTracingProvider>();
Azure::Core::_internal::ClientOptions clientOptions;
clientOptions.Telemetry.TracingProvider = testTracer;
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
Azure::Core::Context callContext = std::move(contextAndSpan.first);
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
{
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
// Add the request ID policy - this adds the x-ms-request-id attribute to the pipeline.
policies.emplace_back(
std::make_unique<RequestActivityPolicy>(Azure::Core::_internal::InputSanitizer{}));
// Final policy - equivalent to HTTP policy.
policies.emplace_back(std::make_unique<NoOpPolicy>());
Azure::Core::Http::_internal::HttpPipeline(policies).Send(request, callContext);
}
EXPECT_EQ(1ul, testTracer->GetTracers().size());
auto& tracer = testTracer->GetTracers().front();
EXPECT_EQ(2ul, tracer->GetSpans().size());
EXPECT_EQ("My API", tracer->GetSpans()[0]->GetName());
EXPECT_EQ("HTTP GET #0", tracer->GetSpans()[1]->GetName());
EXPECT_EQ("GET", tracer->GetSpans()[1]->GetAttributes().at("http.method"));
}
// Now try with the request ID and telemetry policies (simulating a more complete pipeline).
{
auto testTracer = std::make_shared<TestTracingProvider>();
Azure::Core::_internal::ClientOptions clientOptions;
clientOptions.Telemetry.TracingProvider = testTracer;
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
Azure::Core::Context callContext = std::move(contextAndSpan.first);
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
{
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
// Add the request ID policy - this adds the x-ms-request-id attribute to the pipeline.
policies.emplace_back(std::make_unique<RequestIdPolicy>());
policies.emplace_back(
std::make_unique<TelemetryPolicy>("my-service-cpp", "1.0b2", clientOptions.Telemetry));
policies.emplace_back(std::make_unique<RetryPolicy>(RetryOptions{}));
policies.emplace_back(
std::make_unique<RequestActivityPolicy>(Azure::Core::_internal::InputSanitizer{}));
// Final policy - equivalent to HTTP policy.
policies.emplace_back(std::make_unique<NoOpPolicy>());
Azure::Core::Http::_internal::HttpPipeline(policies).Send(request, callContext);
}
EXPECT_EQ(1ul, testTracer->GetTracers().size());
auto& tracer = testTracer->GetTracers().front();
EXPECT_EQ(2ul, tracer->GetSpans().size());
EXPECT_EQ("My API", tracer->GetSpans()[0]->GetName());
EXPECT_EQ("HTTP GET #0", tracer->GetSpans()[1]->GetName());
EXPECT_EQ("GET", tracer->GetSpans()[1]->GetAttributes().at("http.method"));
}
}
TEST(RequestActivityPolicy, TryRetries)
{
{
auto testTracer = std::make_shared<TestTracingProvider>();
Azure::Core::_internal::ClientOptions clientOptions;
clientOptions.Telemetry.TracingProvider = testTracer;
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
Azure::Core::Context callContext = std::move(contextAndSpan.first);
Request request(HttpMethod::Get, Url("https://www.microsoft.com"));
{
std::vector<std::unique_ptr<Azure::Core::Http::Policies::HttpPolicy>> policies;
policies.emplace_back(std::make_unique<RequestIdPolicy>());
policies.emplace_back(std::make_unique<RetryPolicy>(RetryOptions{}));
// Add the request ID policy - this adds the x-ms-request-id attribute to the pipeline.
policies.emplace_back(
std::make_unique<RequestActivityPolicy>(Azure::Core::_internal::InputSanitizer{}));
// Final policy - equivalent to HTTP policy.
int retryCount = 0;
policies.emplace_back(std::make_unique<NoOpPolicy>([&](Request&) {
retryCount += 1;
if (retryCount < 3)
{
// Return a response which should trigger a response.
return std::make_unique<RawResponse>(
1, 1, *RetryOptions().StatusCodes.begin(), "Something");
}
else
{
// Return success.
return std::make_unique<RawResponse>(1, 1, HttpStatusCode::Ok, "Something");
}
}));
Azure::Core::Http::_internal::HttpPipeline pipeline(policies);
// Simulate retrying an HTTP operation 3 times on the pipeline:
pipeline.Send(request, callContext);
}
EXPECT_EQ(1ul, testTracer->GetTracers().size());
auto& tracer = testTracer->GetTracers().front();
EXPECT_EQ(4ul, tracer->GetSpans().size());
EXPECT_EQ("My API", tracer->GetSpans()[0]->GetName());
EXPECT_EQ("HTTP GET #0", tracer->GetSpans()[1]->GetName());
EXPECT_EQ("HTTP GET #1", tracer->GetSpans()[2]->GetName());
EXPECT_EQ("HTTP GET #2", tracer->GetSpans()[3]->GetName());
EXPECT_EQ("GET", tracer->GetSpans()[1]->GetAttributes().at("http.method"));
EXPECT_EQ("408", tracer->GetSpans()[1]->GetAttributes().at("http.status_code"));
EXPECT_EQ("408", tracer->GetSpans()[2]->GetAttributes().at("http.status_code"));
EXPECT_EQ("200", tracer->GetSpans()[3]->GetAttributes().at("http.status_code"));
}
}

View File

@ -48,11 +48,12 @@ TEST(DiagnosticTracingFactory, SimpleServiceSpanTests)
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan("My API", {});
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
EXPECT_FALSE(contextAndSpan.first.IsCancelled());
}
}
namespace {
// Dummy service tracing class.
class TestSpan final : public Azure::Core::Tracing::_internal::Span {
public:
@ -60,6 +61,7 @@ public:
// Inherited via Span
virtual void AddAttributes(AttributeSet const&) override {}
virtual void AddAttribute(std::string const&, std::string 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 {}
@ -67,6 +69,9 @@ public:
// Inherited via Span
virtual void End(Azure::Nullable<Azure::DateTime>) override {}
// Inherited via Span
virtual void PropagateToHttpHeaders(Azure::Core::Http::Request&) override {}
};
class TestAttributeSet : public Azure::Core::Tracing::_internal::AttributeSet {
@ -107,7 +112,7 @@ public:
return std::make_shared<TestTracer>(serviceName, serviceVersion);
};
};
} // namespace
TEST(DiagnosticTracingFactory, BasicServiceSpanTests)
{
{
@ -115,14 +120,16 @@ TEST(DiagnosticTracingFactory, BasicServiceSpanTests)
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan("My API", {});
auto span = std::move(contextAndSpan.second);
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
ServiceSpan 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<TestTracingProvider>();
@ -130,8 +137,9 @@ TEST(DiagnosticTracingFactory, BasicServiceSpanTests)
Azure::Core::Tracing::_internal::DiagnosticTracingFactory serviceTrace(
clientOptions, "my-service-cpp", "1.0b2");
auto contextAndSpan = serviceTrace.CreateSpan("My API", {});
auto span = std::move(contextAndSpan.second);
auto contextAndSpan = serviceTrace.CreateSpan(
"My API", Azure::Core::Tracing::_internal::SpanKind::Internal, {});
ServiceSpan span = std::move(contextAndSpan.second);
span.End();
span.AddEvent("New Event");