|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "google/cloud/opentelemetry/internal/monitoring_exporter.h" |
| 16 | +#include "google/cloud/monitoring/v3/metric_connection.h" |
| 17 | +#include "google/cloud/opentelemetry/internal/time_series.h" |
| 18 | +#include "google/cloud/internal/absl_str_cat_quiet.h" |
| 19 | +#include "google/cloud/internal/noexcept_action.h" |
| 20 | +#include "google/cloud/log.h" |
| 21 | +#include "google/cloud/project.h" |
| 22 | +#include <memory> |
| 23 | + |
| 24 | +namespace google { |
| 25 | +namespace cloud { |
| 26 | +namespace otel_internal { |
| 27 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN |
| 28 | +namespace { |
| 29 | + |
| 30 | +std::string FormatProjectFullName(std::string const& project) { |
| 31 | + return absl::StrCat("projects/", project); |
| 32 | +} |
| 33 | + |
| 34 | +} // namespace |
| 35 | + |
| 36 | +MonitoringExporter::MonitoringExporter( |
| 37 | + std::shared_ptr<monitoring_v3::MetricServiceConnection> conn, |
| 38 | + otel_internal::MonitoredResourceFromDataFn dynamic_resource_fn, |
| 39 | + otel_internal::ResourceFilterDataFn resource_filter_fn, |
| 40 | + Options const& options) |
| 41 | + : client_(std::move(conn)), |
| 42 | + formatter_(options.get<otel::MetricNameFormatterOption>()), |
| 43 | + use_service_time_series_(options.get<otel::ServiceTimeSeriesOption>()), |
| 44 | + mr_proto_(internal::FetchOption<otel::MonitoredResourceOption>(options)), |
| 45 | + dynamic_resource_fn_(std::move(dynamic_resource_fn)), |
| 46 | + resource_filter_fn_(std::move(resource_filter_fn)) {} |
| 47 | + |
| 48 | +MonitoringExporter::MonitoringExporter( |
| 49 | + Project project, |
| 50 | + std::shared_ptr<monitoring_v3::MetricServiceConnection> conn, |
| 51 | + Options const& options) |
| 52 | + : MonitoringExporter(std::move(conn), nullptr, nullptr, options) { |
| 53 | + project_ = std::move(project); |
| 54 | +} |
| 55 | + |
| 56 | +opentelemetry::sdk::common::ExportResult MonitoringExporter::Export( |
| 57 | + opentelemetry::sdk::metrics::ResourceMetrics const& data) noexcept { |
| 58 | + auto result = |
| 59 | + internal::NoExceptAction<opentelemetry::sdk::common::ExportResult>( |
| 60 | + [&] { return ExportImpl(data); }); |
| 61 | + if (result) return *std::move(result); |
| 62 | + GCP_LOG(WARNING) << "Exception thrown while exporting metrics."; |
| 63 | + return opentelemetry::sdk::common::ExportResult::kFailure; |
| 64 | +} |
| 65 | + |
| 66 | +opentelemetry::sdk::common::ExportResult MonitoringExporter::SendRequests( |
| 67 | + std::vector<google::monitoring::v3::CreateTimeSeriesRequest> const& |
| 68 | + requests) { |
| 69 | + auto result = opentelemetry::sdk::common::ExportResult::kSuccess; |
| 70 | + for (auto const& request : requests) { |
| 71 | + auto status = use_service_time_series_ |
| 72 | + ? client_.CreateServiceTimeSeries(request) |
| 73 | + : client_.CreateTimeSeries(request); |
| 74 | + if (status.ok()) continue; |
| 75 | + GCP_LOG(WARNING) << "Cloud Monitoring Export failed with status=" << status; |
| 76 | + // Ultimately, we can only report one error, even though we may send |
| 77 | + // multiple RPCs. If *all* failures are `kInvalidArgument` we should |
| 78 | + // report that. Otherwise, we will report a generic failure. |
| 79 | + if (status.code() == StatusCode::kInvalidArgument && |
| 80 | + result == opentelemetry::sdk::common::ExportResult::kSuccess) { |
| 81 | + result = |
| 82 | + opentelemetry::sdk::common::ExportResult::kFailureInvalidArgument; |
| 83 | + } else if (status.code() != StatusCode::kInvalidArgument) { |
| 84 | + result = opentelemetry::sdk::common::ExportResult::kFailure; |
| 85 | + } |
| 86 | + } |
| 87 | + return result; |
| 88 | +} |
| 89 | + |
| 90 | +opentelemetry::sdk::common::ExportResult MonitoringExporter::ExportImpl( |
| 91 | + opentelemetry::sdk::metrics::ResourceMetrics const& data) { |
| 92 | + opentelemetry::sdk::common::ExportResult result = |
| 93 | + opentelemetry::sdk::common::ExportResult::kSuccess; |
| 94 | + if (otel_internal::IsEmptyTimeSeries(data)) { |
| 95 | + GCP_LOG(INFO) << "Cloud Monitoring Export skipped. No data."; |
| 96 | + // Return early to save the littlest bit of processing. |
| 97 | + return result; |
| 98 | + } |
| 99 | + |
| 100 | + std::vector<google::monitoring::v3::CreateTimeSeriesRequest> requests; |
| 101 | + if (dynamic_resource_fn_) { |
| 102 | + auto tss_map = otel_internal::ToTimeSeriesWithResources( |
| 103 | + data, formatter_, resource_filter_fn_, dynamic_resource_fn_); |
| 104 | + for (auto& tss : tss_map) { |
| 105 | + requests = otel_internal::ToRequests(FormatProjectFullName(tss.first), |
| 106 | + std::move(tss.second)); |
| 107 | + result = SendRequests(requests); |
| 108 | + } |
| 109 | + } else { |
| 110 | + auto tss = otel_internal::ToTimeSeries(data, formatter_); |
| 111 | + auto mr = otel_internal::ToMonitoredResource(data, mr_proto_); |
| 112 | + requests = |
| 113 | + otel_internal::ToRequests(project_->FullName(), mr, std::move(tss)); |
| 114 | + result = SendRequests(requests); |
| 115 | + } |
| 116 | + return result; |
| 117 | +} |
| 118 | + |
| 119 | +Options DefaultOptions(Options o) { |
| 120 | + if (!o.has<otel::MetricNameFormatterOption>()) { |
| 121 | + o.set<otel::MetricNameFormatterOption>([](std::string s) { |
| 122 | + return "workload.googleapis.com/" + std::move(s); |
| 123 | + }); |
| 124 | + } |
| 125 | + return o; |
| 126 | +} |
| 127 | + |
| 128 | +std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter> |
| 129 | +MakeMonitoringExporter(MonitoredResourceFromDataFn dynamic_resource_fn, |
| 130 | + ResourceFilterDataFn resource_filter_fn, |
| 131 | + Options options) { |
| 132 | + auto connection = monitoring_v3::MakeMetricServiceConnection(options); |
| 133 | + options = DefaultOptions(std::move(options)); |
| 134 | + return std::make_unique<otel_internal::MonitoringExporter>( |
| 135 | + std::move(connection), std::move(dynamic_resource_fn), |
| 136 | + std::move(resource_filter_fn), std::move(options)); |
| 137 | +} |
| 138 | + |
| 139 | +std::unique_ptr<opentelemetry::sdk::metrics::PushMetricExporter> |
| 140 | +MakeMonitoringExporter( |
| 141 | + MonitoredResourceFromDataFn dynamic_resource_fn, |
| 142 | + ResourceFilterDataFn resource_filter_fn, |
| 143 | + std::shared_ptr<monitoring_v3::MetricServiceConnection> conn, |
| 144 | + Options options) { |
| 145 | + options = DefaultOptions(std::move(options)); |
| 146 | + return std::make_unique<otel_internal::MonitoringExporter>( |
| 147 | + std::move(conn), std::move(dynamic_resource_fn), |
| 148 | + std::move(resource_filter_fn), std::move(options)); |
| 149 | +} |
| 150 | + |
| 151 | +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END |
| 152 | +} // namespace otel_internal |
| 153 | +} // namespace cloud |
| 154 | +} // namespace google |
0 commit comments