1
#include "source/extensions/tracers/opentelemetry/resource_detectors/resource_provider.h"
2

            
3
#include <string>
4

            
5
#include "source/common/common/logger.h"
6
#include "source/common/config/utility.h"
7
#include "source/common/version/version.h"
8

            
9
namespace Envoy {
10
namespace Extensions {
11
namespace Tracers {
12
namespace OpenTelemetry {
13

            
14
namespace {
15
13
bool isEmptyResource(const Resource& resource) { return resource.attributes_.empty(); }
16

            
17
57
Resource createInitialResource(absl::string_view service_name) {
18
57
  Resource resource{};
19

            
20
  // Creates initial resource with the static service.name and telemetry.sdk.* attributes.
21
57
  if (!service_name.empty()) {
22
43
    resource.attributes_[kServiceNameKey] = service_name;
23
43
  }
24
57
  resource.attributes_[kTelemetrySdkLanguageKey] = kDefaultTelemetrySdkLanguage;
25

            
26
57
  resource.attributes_[kTelemetrySdkNameKey] = kDefaultTelemetrySdkName;
27

            
28
57
  resource.attributes_[kTelemetrySdkVersionKey] = Envoy::VersionInfo::version();
29

            
30
57
  return resource;
31
57
}
32

            
33
/**
34
 * @brief Resolves the new schema url when merging two resources.
35
 * This function implements the algorithm as defined in the OpenTelemetry Resource SDK
36
 * specification. @see
37
 * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#merge
38
 *
39
 * @param old_schema_url The old resource's schema URL.
40
 * @param updating_schema_url The updating resource's schema URL.
41
 * @return std::string The calculated schema URL.
42
 */
43
std::string resolveSchemaUrl(const std::string& old_schema_url,
44
13
                             const std::string& updating_schema_url) {
45
13
  if (old_schema_url.empty()) {
46
10
    return updating_schema_url;
47
10
  }
48
3
  if (updating_schema_url.empty()) {
49
1
    return old_schema_url;
50
1
  }
51
2
  if (old_schema_url == updating_schema_url) {
52
1
    return old_schema_url;
53
1
  }
54
  // The OTel spec leaves this case (when both have value but are different) unspecified.
55
1
  ENVOY_LOG_MISC(info, "Resource schemaUrl conflict. Fall-back to old schema url: {}",
56
1
                 old_schema_url);
57
1
  return old_schema_url;
58
2
}
59

            
60
/**
61
 * @brief Updates an old resource with a new one. This function implements
62
 * the Merge operation defined in the OpenTelemetry Resource SDK specification.
63
 * @see
64
 * https://github.com/open-telemetry/opentelemetry-specification/blob/v1.24.0/specification/resource/sdk.md#merge
65
 *
66
 * @param old_resource The old resource.
67
 * @param updating_resource The new resource.
68
 */
69
13
void mergeResource(Resource& old_resource, const Resource& updating_resource) {
70
  // The schemaUrl is merged, regardless if the resources being merged
71
  // have attributes or not. This behavior is compliant with the OTel spec.
72
  // see: https://github.com/envoyproxy/envoy/pull/29547#discussion_r1344540427
73
13
  old_resource.schema_url_ =
74
13
      resolveSchemaUrl(old_resource.schema_url_, updating_resource.schema_url_);
75

            
76
13
  if (isEmptyResource(updating_resource)) {
77
3
    return;
78
3
  }
79
11
  for (auto const& attr : updating_resource.attributes_) {
80
11
    old_resource.attributes_.insert_or_assign(attr.first, attr.second);
81
11
  }
82
10
}
83
} // namespace
84

            
85
Resource ResourceProviderImpl::getResource(
86
    const Protobuf::RepeatedPtrField<envoy::config::core::v3::TypedExtensionConfig>&
87
        resource_detectors,
88
    Envoy::Server::Configuration::ServerFactoryContext& context,
89
57
    absl::string_view service_name) const {
90

            
91
57
  Resource resource = createInitialResource(service_name);
92

            
93
61
  for (const auto& detector_config : resource_detectors) {
94
15
    ResourceDetectorPtr detector;
95
15
    auto* factory = Envoy::Config::Utility::getFactory<ResourceDetectorFactory>(detector_config);
96

            
97
15
    if (!factory) {
98
1
      throw EnvoyException(
99
1
          fmt::format("Resource detector factory not found: '{}'", detector_config.name()));
100
1
    }
101

            
102
14
    detector = factory->createResourceDetector(detector_config.typed_config(), context);
103

            
104
14
    if (!detector) {
105
1
      throw EnvoyException(
106
1
          fmt::format("Resource detector could not be created: '{}'", detector_config.name()));
107
1
    }
108

            
109
13
    Resource detected_resource = detector->detect();
110
13
    mergeResource(resource, detected_resource);
111
13
  }
112
55
  return resource;
113
57
}
114

            
115
} // namespace OpenTelemetry
116
} // namespace Tracers
117
} // namespace Extensions
118
} // namespace Envoy