-
Notifications
You must be signed in to change notification settings - Fork 438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding semantic-convention attributes for resources #872
Changes from 5 commits
053e9a0
af9150a
42fdfa2
79f2299
7c92a7b
56060aa
a5184e4
98c8b14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include "opentelemetry/version.h" | ||
|
||
#define OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(attribute_name, attribute_value) \ | ||
static constexpr const char *GetAttribute##attribute_name() noexcept { return attribute_value; } | ||
|
||
OPENTELEMETRY_BEGIN_NAMESPACE | ||
namespace sdk | ||
{ | ||
namespace resource | ||
{ | ||
/** | ||
* Stores the Constants for semantic Attribute names for resources outlined by the OpenTelemetry | ||
* specifications. <see | ||
* href,"https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md"/>. | ||
*/ | ||
|
||
class SemanticConventions final | ||
{ | ||
|
||
public: | ||
// service attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ServiceName, "service.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ServiceNamespace, "service.namespace"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ServiceInstance, "service.instance.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ServiceVersion, "service.version "); | ||
|
||
// telemetry attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(TelemetrySdkName, "telemetry.sdk.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(TelemetrySdkLanguage, "telemetry.sdk.language"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(TelemetrySdkVersion, "telemetry.sdk.version"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(TelemetryAutoVersion, "telemetry.auto.version"); | ||
|
||
// compute unit: container attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ContainerName, "container.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ContainerId, "container.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ContainerRuntime, "container.runtime"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ContainerImageName, "container.image.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ContainerImageTag, "container.image.tag"); | ||
|
||
// compute unit: faas attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(FaasName, "faas.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(FaasId, "faas.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(FaasVersion, "faas.version"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(FaasInstance, "faas.instance"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(FaasMaxMemory, "faas.max_memory"); | ||
|
||
// compute unit : process attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessId, "process.pid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessExecutableName, "process.executable.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessExecutablePath, "process.executable.path"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessCommand, "process.command"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessCommandLine, "process.command_line"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessCommandArgs, "process.command_args"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessOwner, "process.owner"); | ||
|
||
// compute : process runtimes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessRuntimeName, "process.runtime.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessRuntimeVersion, "process.runtime.version"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(ProcessRuntimeDescription, "process.runtime.description"); | ||
|
||
// compute unit : WebEngine | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(WebEngineName, "webengine.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(WebEngineVersion, "webengine.version"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(WebEngineDescription, "webengine.description"); | ||
|
||
// compute instance : host | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostId, "host.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostName, "host.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostType, "host.type"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostArch, "host.arch"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostImageName, "host.image.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostImageId, "host.image.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(HostImageVersion, "host.image.version"); | ||
|
||
// env os attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(OsType, "os.type"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(OsDescription, "os.description"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(OsName, "os.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(OsVersion, "os.version"); | ||
|
||
// env device attributes | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(DeviceId, "device.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(DeviceModelIdentifier, "device.model.identifier"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(DeviceModelName, "device.model.name"); | ||
|
||
// env cloud | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CloudProvider, "cloud.provider"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CloudAccountId, "cloud.account.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CloudRegion, "cloud.region"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CloudAvailabilityZone, "cloud.availability_zone"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CloudPlatform, "cloud.platform"); | ||
|
||
// env deployment | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(DeploymentEnvironment, "deployment.environment"); | ||
|
||
// env kubernetes | ||
// - cluster | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sClusterName, "k8s.cluster.name"); | ||
// - node | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sNodeName, "k8s.node.name"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sNodeUid, "k8s.node.uid"); | ||
// - namespace | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sNamespaceName, "k8s.namespace.name"); | ||
// - pod | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sPodUid, "k8s.pod.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sPodName, "k8s.pod.name"); | ||
// - container | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sContainerName, "k8s.container.name"); | ||
// - replicaset | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sReplicaSetUid, "k8s.replicaset.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sReplicaSetName, "k8s.replicaset.name"); | ||
// - deployment | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sDeploymentUid, "k8s.deployment.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sDeploymentName, "k8s.deployment.name"); | ||
// - stateful-set | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sStatefulSetUid, "k8s.statefulset.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sStatefulSetName, "k8s.statefulset.name"); | ||
// - daemon set | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sDaemonSetUid, "k8s.daemonset.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sDaemonSetName, "k8s.daemonset.name"); | ||
// - job | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sJobUid, "k8s.job.uid"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(K8sJobName, "k8s.job.name"); | ||
// - cronjob | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CronjobUid, "k8s.cronjob.id"); | ||
OTEL_RESOURCE_GENERATE_SEMCONV_METHOD(CronjobName, "k8s.cronjob.name"); | ||
}; | ||
|
||
} // namespace resource | ||
} // namespace sdk | ||
OPENTELEMETRY_END_NAMESPACE |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
#include "opentelemetry/nostd/string_view.h" | ||
#include "opentelemetry/sdk/common/attribute_utils.h" | ||
#include "opentelemetry/sdk/resource/resource_detector.h" | ||
#include "opentelemetry/sdk/resource/semantic_conventions.h" | ||
|
||
#include <cstdlib> | ||
#include <string> | ||
|
@@ -17,30 +18,29 @@ | |
# define putenv _putenv | ||
#endif | ||
|
||
class TestResource : public opentelemetry::sdk::resource::Resource | ||
using namespace opentelemetry::sdk::resource; | ||
|
||
class TestResource : public Resource | ||
{ | ||
public: | ||
TestResource(opentelemetry::sdk::resource::ResourceAttributes attributes = | ||
opentelemetry::sdk::resource::ResourceAttributes()) | ||
: Resource(attributes) | ||
{} | ||
TestResource(ResourceAttributes attributes = ResourceAttributes()) : Resource(attributes) {} | ||
}; | ||
|
||
TEST(ResourceTest, create_without_servicename) | ||
{ | ||
|
||
opentelemetry::sdk::resource::ResourceAttributes expected_attributes = { | ||
ResourceAttributes expected_attributes = { | ||
{"service", "backend"}, | ||
{"version", (uint32_t)1}, | ||
{"cost", 234.23}, | ||
{"telemetry.sdk.language", "cpp"}, | ||
{"telemetry.sdk.name", "opentelemetry"}, | ||
{"telemetry.sdk.version", OPENTELEMETRY_SDK_VERSION}, | ||
{"service.name", "unknown_service"}}; | ||
{SemanticConventions::GetAttributeTelemetrySdkLanguage(), "cpp"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I honestly would prefer an API model similar to Qt5 "translation resources", which solves the same problem - just in a different knowledge domain. More on that here. To elaborate:
That way instead of having N getters, which will also present a challenge - should you ever need to project this C++ code to C#, Python or Javascript/Node, etc... you'd have ONE attribute semantic convention "translation" method - called using namespace AttributeNames;
...
{attr(OTEL_ATTR_SDK_LANGUAGE), "cpp"}
... // or
{attr("sdk.language"), "cpp"} Then we should strive the transform to be done at compile time, without generating those 50 methods. It does not scale. It'd scale nicely if you have one method / one utility function, hopefully Again, If you want to enforce , such as - if attribute has not been found in the lookup table, fail compilation (not allowing users to pass invalid semantic convention parameters) - we can use a The model with |
||
{SemanticConventions::GetAttributeTelemetrySdkName(), "opentelemetry"}, | ||
{SemanticConventions::GetAttributeTelemetrySdkVersion(), OPENTELEMETRY_SDK_VERSION}, | ||
{SemanticConventions::GetAttributeServiceName(), "unknown_service"}}; | ||
|
||
opentelemetry::sdk::resource::ResourceAttributes attributes = { | ||
ResourceAttributes attributes = { | ||
{"service", "backend"}, {"version", (uint32_t)1}, {"cost", 234.23}}; | ||
auto resource = opentelemetry::sdk::resource::Resource::Create(attributes); | ||
auto resource = Resource::Create(attributes); | ||
auto received_attributes = resource.GetAttributes(); | ||
for (auto &e : received_attributes) | ||
{ | ||
|
@@ -61,17 +61,17 @@ TEST(ResourceTest, create_without_servicename) | |
|
||
TEST(ResourceTest, create_with_servicename) | ||
{ | ||
opentelemetry::sdk::resource::ResourceAttributes expected_attributes = { | ||
ResourceAttributes expected_attributes = { | ||
{"version", (uint32_t)1}, | ||
{"cost", 234.23}, | ||
{"telemetry.sdk.language", "cpp"}, | ||
{"telemetry.sdk.name", "opentelemetry"}, | ||
{"telemetry.sdk.version", OPENTELEMETRY_SDK_VERSION}, | ||
{"service.name", "backend"}, | ||
{SemanticConventions::GetAttributeTelemetrySdkLanguage(), "cpp"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually to elaborate on my idea - you probably do not even need to change anything. Keep passing original string literals. Maybe replace them by constants, such as Then we have an assignment operator in the If you need concrete example, I can send some WIP code to illustrate the model? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @maxgolov for the elaboration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can definitely avoid getters. OR - at least, it all boils down to a single getter method ( I feel like more concrete code example (based off above constexpr hash calculation) would be helpful? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @maxgolov I have done the changes (hopefully) as per your suggestion :) |
||
{SemanticConventions::GetAttributeTelemetrySdkName(), "opentelemetry"}, | ||
{SemanticConventions::GetAttributeTelemetrySdkVersion(), OPENTELEMETRY_SDK_VERSION}, | ||
{SemanticConventions::GetAttributeServiceName(), "backend"}, | ||
}; | ||
opentelemetry::sdk::resource::ResourceAttributes attributes = { | ||
ResourceAttributes attributes = { | ||
{"service.name", "backend"}, {"version", (uint32_t)1}, {"cost", 234.23}}; | ||
auto resource = opentelemetry::sdk::resource::Resource::Create(attributes); | ||
auto resource = Resource::Create(attributes); | ||
auto received_attributes = resource.GetAttributes(); | ||
for (auto &e : received_attributes) | ||
{ | ||
|
@@ -94,15 +94,15 @@ TEST(ResourceTest, create_with_servicename) | |
|
||
TEST(ResourceTest, create_with_emptyatrributes) | ||
{ | ||
opentelemetry::sdk::resource::ResourceAttributes expected_attributes = { | ||
{"telemetry.sdk.language", "cpp"}, | ||
{"telemetry.sdk.name", "opentelemetry"}, | ||
{"telemetry.sdk.version", OPENTELEMETRY_SDK_VERSION}, | ||
{"service.name", "unknown_service"}, | ||
ResourceAttributes expected_attributes = { | ||
{SemanticConventions::GetAttributeTelemetrySdkLanguage(), "cpp"}, | ||
{SemanticConventions::GetAttributeTelemetrySdkName(), "opentelemetry"}, | ||
{SemanticConventions::GetAttributeTelemetrySdkVersion(), OPENTELEMETRY_SDK_VERSION}, | ||
{SemanticConventions::GetAttributeServiceName(), "unknown_service"}, | ||
}; | ||
opentelemetry::sdk::resource::ResourceAttributes attributes = {}; | ||
auto resource = opentelemetry::sdk::resource::Resource::Create(attributes); | ||
auto received_attributes = resource.GetAttributes(); | ||
ResourceAttributes attributes = {}; | ||
auto resource = Resource::Create(attributes); | ||
auto received_attributes = resource.GetAttributes(); | ||
for (auto &e : received_attributes) | ||
{ | ||
EXPECT_TRUE(expected_attributes.find(e.first) != expected_attributes.end()); | ||
|
@@ -114,10 +114,8 @@ TEST(ResourceTest, create_with_emptyatrributes) | |
} | ||
TEST(ResourceTest, Merge) | ||
{ | ||
TestResource resource1( | ||
opentelemetry::sdk::resource::ResourceAttributes({{"service", "backend"}})); | ||
TestResource resource2( | ||
opentelemetry::sdk::resource::ResourceAttributes({{"host", "service-host"}})); | ||
TestResource resource1(ResourceAttributes({{"service", "backend"}})); | ||
TestResource resource2(ResourceAttributes({{"host", "service-host"}})); | ||
std::map<std::string, std::string> expected_attributes = {{"service", "backend"}, | ||
{"host", "service-host"}}; | ||
|
||
|
@@ -164,7 +162,7 @@ TEST(ResourceTest, OtelResourceDetector) | |
char env[] = "OTEL_RESOURCE_ATTRIBUTES=k=v"; | ||
putenv(env); | ||
|
||
opentelemetry::sdk::resource::OTELResourceDetector detector; | ||
OTELResourceDetector detector; | ||
auto resource = detector.Detect(); | ||
auto received_attributes = resource.GetAttributes(); | ||
for (auto &e : received_attributes) | ||
|
@@ -192,7 +190,7 @@ TEST(ResourceTest, OtelResourceDetectorEmptyEnv) | |
#else | ||
unsetenv("OTEL_RESOURCE_ATTRIBUTES"); | ||
#endif | ||
opentelemetry::sdk::resource::OTELResourceDetector detector; | ||
OTELResourceDetector detector; | ||
auto resource = detector.Detect(); | ||
auto received_attributes = resource.GetAttributes(); | ||
for (auto &e : received_attributes) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do this instead. Take this compile-time hash function:
Then for every attribute, generate a table of values. At compile time - by listing all of them. Sorry for using this example - something like this:
So - you created a compile time table that maps your attribute names to unique IDs. You can still refer to original attributes by their aliases. Like
ATTR_ServiceName
, macro-magic permitting.The goal is that you can do tricks like switch-case, constexpr, and mapping from attribute to unique attribute id. Then instead of many methods, you end up with
attr(name)
orattr(id)
. That's how Qt5tr()
works, and how translations / resources work in Android pretty much. Except thattr()
requires string literal, and Android resources are all revolving aroundgetResource(id)
.In our model, I think, it'd be more appropriate to perform compile-time mapping from
attr("my.attribute.name")
-> returns actual attribute name within given semantic convention. Not many getters. One getter. Lookup table. And preferably, compile-time lookup, not method call.