prod/native/libphpbridge/code/OtlpExporter/SpanConverter.h (187 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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.
*/
#pragma once
#include "opentelemetry/proto/trace/v1/trace.pb.h"
#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h"
#include "AttributesConverter.h"
#include "ConverterHelpers.h"
#include "AutoZval.h"
#include "CiCharTraits.h"
#include <string>
#include <string_view>
#include <unordered_map>
namespace elasticapm::php {
using namespace std::string_view_literals;
class SpanConverter {
public:
std::string getStringSerialized(AutoZval const &batch) {
return convert(batch).SerializeAsString();
}
opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest convert(AutoZval const &spans) {
opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request;
std::unordered_map<std::string, opentelemetry::proto::trace::v1::ResourceSpans *> resourceSpansMap;
std::unordered_map<std::string, opentelemetry::proto::trace::v1::ScopeSpans *> scopeSpansMap;
if (!spans.isArray()) {
throw std::runtime_error("Invalid iterable passed to SpanConverter");
}
for (auto const &span : spans) {
// auto internalSpan = span.readProperty("span");
// auto resourceInfo = internalSpan.readProperty("resource");
// auto instrumentationScope = internalSpan.readProperty("instrumentationScope");
auto resourceInfo = span.assertObjectType("OpenTelemetry\\SDK\\Trace\\ImmutableSpan"sv).callMethod("getResource"sv); // ResourceInfo
auto instrumentationScope = span.callMethod("getInstrumentationScope"sv); // InstrumentationScopeInterface
std::string resourceId = ConverterHelpers::getResourceId(resourceInfo);
std::string scopeId = ConverterHelpers::getScopeId(instrumentationScope);
opentelemetry::proto::trace::v1::ResourceSpans *resourceSpans;
if (decltype(resourceSpansMap)::iterator resourceSpansIt = resourceSpansMap.find(resourceId); resourceSpansIt == resourceSpansMap.end()) {
resourceSpans = request.add_resource_spans();
convertResourceSpans(resourceInfo, resourceSpans);
resourceSpansMap[resourceId] = resourceSpans;
} else {
resourceSpans = resourceSpansIt->second;
}
opentelemetry::proto::trace::v1::ScopeSpans *scopeSpans;
std::string compositeKey = resourceId + "|" + scopeId;
if (scopeSpansMap.count(compositeKey) == 0) {
scopeSpans = resourceSpans->add_scope_spans();
convertScopeSpans(instrumentationScope, scopeSpans);
scopeSpansMap[compositeKey] = scopeSpans;
} else {
scopeSpans = scopeSpansMap[compositeKey];
}
opentelemetry::proto::trace::v1::Span *outSpan = scopeSpans->add_spans();
convertSpan(span, outSpan);
}
return request;
}
private:
void convertResourceSpans(elasticapm::php::AutoZval &resourceInfo, opentelemetry::proto::trace::v1::ResourceSpans *out) {
if (auto schemaUrl = resourceInfo.callMethod("getSchemaUrl"sv); schemaUrl.isString()) {
out->set_schema_url(schemaUrl.getStringView());
}
opentelemetry::proto::resource::v1::Resource *resource = out->mutable_resource();
auto attributes = resourceInfo.callMethod("getAttributes"sv);
AttributesConverter::convertAttributes(attributes, resource->mutable_attributes());
resource->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong());
}
void convertScopeSpans(elasticapm::php::AutoZval &instrumentationScope, opentelemetry::proto::trace::v1::ScopeSpans *out) {
opentelemetry::proto::common::v1::InstrumentationScope *scope = out->mutable_scope();
scope->set_name(instrumentationScope.callMethod("getName"sv).getStringView());
if (auto version = instrumentationScope.callMethod("getVersion"sv); version.isString()) {
scope->set_version(version.getStringView());
}
auto attributes = instrumentationScope.callMethod("getAttributes"sv);
AttributesConverter::convertAttributes(attributes, scope->mutable_attributes());
scope->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong());
if (auto schemaUrl = instrumentationScope.callMethod("getSchemaUrl"sv); schemaUrl.isString()) {
out->set_schema_url(schemaUrl.getStringView());
}
}
opentelemetry::proto::trace::v1::Span_SpanKind convertSpanKind(int kind) {
using opentelemetry::proto::trace::v1::Span_SpanKind;
constexpr int KIND_INTERNAL = 0;
constexpr int KIND_CLIENT = 1;
constexpr int KIND_SERVER = 2;
constexpr int KIND_PRODUCER = 3;
constexpr int KIND_CONSUMER = 4;
switch (kind) {
case KIND_INTERNAL:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_INTERNAL;
case KIND_CLIENT:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_CLIENT;
case KIND_SERVER:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_SERVER;
case KIND_PRODUCER:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_PRODUCER;
case KIND_CONSUMER:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_CONSUMER;
default:
return Span_SpanKind::Span_SpanKind_SPAN_KIND_UNSPECIFIED;
}
}
opentelemetry::proto::trace::v1::Status_StatusCode convertStatusCode(std::string_view status) {
using opentelemetry::proto::trace::v1::Status_StatusCode;
using namespace elasticapm::utils::string_view_literals;
auto iStatus = elasticapm::utils::traits_cast<elasticapm::utils::CiCharTraits>(status);
if (iStatus == "Unset"_cisv) {
return Status_StatusCode::Status_StatusCode_STATUS_CODE_UNSET;
} else if (iStatus == "Ok"_cisv) {
return Status_StatusCode::Status_StatusCode_STATUS_CODE_OK;
} else if (iStatus == "Error"_cisv) {
return Status_StatusCode::Status_StatusCode_STATUS_CODE_ERROR;
}
return Status_StatusCode::Status_StatusCode_STATUS_CODE_UNSET;
}
int addRemoteFlags(elasticapm::php::AutoZval &spanContext, int baseFlags) {
using namespace std::string_view_literals;
int flags = baseFlags;
flags |= opentelemetry::proto::trace::v1::SpanFlags::SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK;
if (spanContext.callMethod("isRemote"sv).getBoolean()) {
flags |= opentelemetry::proto::trace::v1::SpanFlags::SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK;
}
return flags;
}
int buildFlagsForSpan(elasticapm::php::AutoZval &spanContext, elasticapm::php::AutoZval &parentSpanContext) {
int flags = spanContext.callMethod("getTraceFlags"sv, {}).getLong();
/**
* @see https://github.com/open-telemetry/opentelemetry-proto/blob/v1.5.0/opentelemetry/proto/trace/v1/trace.proto#L122
*
* Bits 8 and 9 represent the 3 states of whether a span's parent is remote.
* ^^^^^^
* That is why we pass parent span's context.
*/
return addRemoteFlags(parentSpanContext, flags);
}
int buildFlagsForLink(elasticapm::php::AutoZval &linkSpanContext) {
int flags = linkSpanContext.callMethod("getTraceFlags"sv, {}).getLong();
/**
* @see https://github.com/open-telemetry/opentelemetry-proto/blob/v1.5.0/opentelemetry/proto/trace/v1/trace.proto#L279
*
* Bits 8 and 9 represent the 3 states of whether the link is remote.
* ^^^^
* That is why we pass link span's context.
*/
return addRemoteFlags(linkSpanContext, flags);
}
void convertSpan(elasticapm::php::AutoZval const &span, opentelemetry::proto::trace::v1::Span *out) {
using namespace opentelemetry::proto::trace::v1;
using opentelemetry::proto::trace::v1::Status;
using namespace std::string_view_literals;
auto context = span.callMethod("getContext"sv);
out->set_trace_id(context.callMethod("getTraceIdBinary"sv).getStringView());
out->set_span_id(context.callMethod("getSpanIdBinary"sv).getStringView());
auto parentSpanContext = span.callMethod("getParentContext"sv);
out->set_flags(buildFlagsForSpan(context, parentSpanContext));
auto traceState = context.callMethod("getTraceState"sv);
if (traceState.isObject()) {
out->set_trace_state(traceState.callMethod("__toString"sv, {}).getStringView());
}
if (parentSpanContext.callMethod("isValid"sv).getBoolean()) {
out->set_parent_span_id(parentSpanContext.callMethod("getSpanIdBinary"sv).getStringView());
}
out->set_name(span.callMethod("getName"sv).getStringView());
out->set_kind(convertSpanKind(span.callMethod("getKind"sv).getLong()));
out->set_start_time_unix_nano(span.callMethod("getStartEpochNanos"sv).getLong());
out->set_end_time_unix_nano(span.callMethod("getEndEpochNanos"sv).getLong());
{
auto attributes = span.callMethod("getAttributes"sv);
out->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong());
AttributesConverter::convertAttributes(attributes, out->mutable_attributes());
}
{
auto events = span.callMethod("getEvents"sv);
for (auto const &event : events) {
opentelemetry::proto::trace::v1::Span::Event *outEvent = out->add_events();
outEvent->set_time_unix_nano(event.callMethod("getEpochNanos"sv).getLong());
outEvent->set_name(event.callMethod("getName"sv).getStringView());
auto attributes = event.callMethod("getAttributes"sv);
AttributesConverter::convertAttributes(attributes, outEvent->mutable_attributes());
outEvent->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong());
}
out->set_dropped_events_count(span.callMethod("getTotalDroppedEvents"sv).getLong());
}
auto links = span.callMethod("getLinks"sv);
for (auto const &link : links) {
opentelemetry::proto::trace::v1::Span::Link *outLink = out->add_links();
{
auto linkSpanContext = link.callMethod("getSpanContext"sv);
outLink->set_trace_id(linkSpanContext.callMethod("getTraceIdBinary"sv).getStringView());
outLink->set_span_id(linkSpanContext.callMethod("getSpanIdBinary"sv).getStringView());
outLink->set_flags(buildFlagsForLink(linkSpanContext));
if (auto traceState = linkSpanContext.callMethod("getTraceState"sv); traceState.isObject()) {
outLink->set_trace_state(traceState.callMethod("__toString"sv).getStringView());
}
}
auto attributes = link.callMethod("getAttributes"sv);
AttributesConverter::convertAttributes(attributes, outLink->mutable_attributes());
outLink->set_dropped_attributes_count(attributes.callMethod("getDroppedAttributesCount"sv).getLong());
}
out->set_dropped_links_count(span.callMethod("getTotalDroppedLinks"sv).getLong());
Status *outStatus = out->mutable_status();
auto status = span.callMethod("getStatus"sv);
outStatus->set_message(status.callMethod("getDescription"sv).getStringView());
outStatus->set_code(convertStatusCode(status.callMethod("getCode"sv).getStringView()));
}
};
} // namespace elasticapm::php