1/*
2* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3*
4* Licensed under the Apache License, Version 2.0 (the "License").
5* You may not use this file except in compliance with the License.
6* A copy of the License is located at
7*
8* http://aws.amazon.com/apache2.0
9*
10* or in the "license" file accompanying this file. This file is distributed
11* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12* express or implied. See the License for the specific language governing
13* permissions and limitations under the License.
14*/
15
16#include <aws/core/utils/memory/AWSMemory.h>
17#include <aws/core/monitoring/DefaultMonitoring.h>
18#include <aws/core/utils/DateTime.h>
19#include <aws/core/utils/json/JsonSerializer.h>
20#include <aws/core/utils/Outcome.h>
21#include <aws/core/client/AWSClient.h>
22#include <aws/core/auth/AWSCredentialsProvider.h>
23#include <aws/core/platform/Environment.h>
24#include <aws/core/config/AWSProfileConfigLoader.h>
25#include <aws/core/utils/logging/LogMacros.h>
26using namespace Aws::Utils;
27
28namespace Aws
29{
30 namespace Monitoring
31 {
32 static const char DEFAULT_MONITORING_ALLOC_TAG[] = "DefaultMonitoringAllocTag";
33 static const int CLIENT_ID_LENGTH_LIMIT = 256;
34 static const int USER_AGENT_LENGHT_LIMIT = 256;
35 static const int ERROR_MESSAGE_LENGTH_LIMIT = 512;
36
37 const char DEFAULT_MONITORING_CLIENT_ID[] = ""; // default to empty;
38 const char DEFAULT_MONITORING_HOST[] = "127.0.0.1"; // default to loopback ip address instead of "localhost" based on design specification.
39 unsigned short DEFAULT_MONITORING_PORT = 31000; //default to 31000;
40 bool DEFAULT_MONITORING_ENABLE = false; //default to false;
41
42 const int DefaultMonitoring::DEFAULT_MONITORING_VERSION = 1;
43 const char DefaultMonitoring::DEFAULT_CSM_CONFIG_ENABLED[] = "csm_enabled";
44 const char DefaultMonitoring::DEFAULT_CSM_CONFIG_CLIENT_ID[] = "csm_client_id";
45 const char DefaultMonitoring::DEFAULT_CSM_CONFIG_HOST[] = "csm_host";
46 const char DefaultMonitoring::DEFAULT_CSM_CONFIG_PORT[] = "csm_port";
47 const char DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_ENABLED[] = "AWS_CSM_ENABLED";
48 const char DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_CLIENT_ID[] = "AWS_CSM_CLIENT_ID";
49 const char DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_HOST[] = "AWS_CSM_HOST";
50 const char DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_PORT[] = "AWS_CSM_PORT";
51
52
53 struct DefaultContext
54 {
55 Aws::Utils::DateTime apiCallStartTime;
56 Aws::Utils::DateTime attemptStartTime;
57 int retryCount = 0;
58 bool lastAttemptSucceeded = false;
59 bool lastErrorRetriable = false; //dosen't apply if last attempt succeeded.
60 const Aws::Client::HttpResponseOutcome* outcome = nullptr;
61 };
62
63 static inline void FillRequiredFieldsToJson(Json::JsonValue& json,
64 const Aws::String& type,
65 const Aws::String& service,
66 const Aws::String& api,
67 const Aws::String& clientId,
68 const DateTime& timestamp,
69 int version,
70 const Aws::String& userAgent)
71 {
72 json.WithString("Type", type)
73 .WithString("Service", service)
74 .WithString("Api", api)
75 .WithString("ClientId", clientId.substr(0, CLIENT_ID_LENGTH_LIMIT))
76 .WithInt64("Timestamp", timestamp.Millis())
77 .WithInteger("Version", version)
78 .WithString("UserAgent", userAgent.substr(0, USER_AGENT_LENGHT_LIMIT));
79 }
80
81 static inline void FillRequiredApiCallFieldsToJson(Json::JsonValue& json,
82 int attemptCount,
83 int64_t apiCallLatency,
84 bool maxRetriesExceeded)
85 {
86 json.WithInteger("AttemptCount", attemptCount)
87 .WithInt64("Latency", apiCallLatency)
88 .WithInteger("MaxRetriesExceeded", maxRetriesExceeded ? 1 : 0);
89 }
90
91 static inline void FillRequiredApiAttemptFieldsToJson(Json::JsonValue& json,
92 const Aws::String& domainName,
93 int64_t attemptLatency)
94 {
95 json.WithString("Fqdn", domainName)
96 .WithInt64("AttemptLatency", attemptLatency);
97 }
98
99 static inline void ExportResponseHeaderToJson(Json::JsonValue& json, const Aws::Http::HeaderValueCollection& headers,
100 const Aws::String& headerName, const Aws::String& targetName)
101 {
102 auto iter = headers.find(headerName);
103 if (iter != headers.end())
104 {
105 json.WithString(targetName, iter->second);
106 }
107 }
108
109 static inline void ExportHttpMetricsToJson(Json::JsonValue& json, const Aws::Monitoring::HttpClientMetricsCollection& httpMetrics, Aws::Monitoring::HttpClientMetricsType type)
110 {
111 auto iter = httpMetrics.find(GetHttpClientMetricNameByType(type));
112 if (iter != httpMetrics.end())
113 {
114 json.WithInt64(GetHttpClientMetricNameByType(type), iter->second);
115 }
116 }
117
118 static inline void FillOptionalApiCallFieldsToJson(Json::JsonValue& json,
119 const Aws::Http::HttpRequest* request,
120 const Aws::Client::HttpResponseOutcome& outcome)
121 {
122 if (!request->GetSigningRegion().empty())
123 {
124 json.WithString("Region", request->GetSigningRegion());
125 }
126 if (!outcome.IsSuccess())
127 {
128 if (outcome.GetError().GetExceptionName().empty()) // Not Aws Excecption
129 {
130 json.WithString("FinalSdkExceptionMessage", outcome.GetError().GetMessage().substr(0, ERROR_MESSAGE_LENGTH_LIMIT));
131 }
132 else // Aws Exception
133 {
134 json.WithString("FinalAwsException", outcome.GetError().GetExceptionName())
135 .WithString("FinalAwsExceptionMessage", outcome.GetError().GetMessage().substr(0, ERROR_MESSAGE_LENGTH_LIMIT));
136 }
137 json.WithInteger("FinalHttpStatusCode", static_cast<int>(outcome.GetError().GetResponseCode()));
138 }
139 else
140 {
141 json.WithInteger("FinalHttpStatusCode", static_cast<int>(outcome.GetResult()->GetResponseCode()));
142 }
143 }
144
145 static inline void FillOptionalApiAttemptFieldsToJson(Json::JsonValue& json,
146 const Aws::Http::HttpRequest* request,
147 const Aws::Client::HttpResponseOutcome& outcome,
148 const CoreMetricsCollection& metricsFromCore)
149 {
150 /**
151 *No matter request succeeded or not, these fields should be included as long as their requirements
152 *are met. We should be able to access response (so as to access original requeset) if the response has error.
153 */
154 if (request->HasAwsSessionToken() && !request->GetAwsSessionToken().empty())
155 {
156 json.WithString("SessionToken", request->GetAwsSessionToken());
157 }
158 if (!request->GetSigningRegion().empty())
159 {
160 json.WithString("Region", request->GetSigningRegion());
161 }
162 if (!request->GetSigningAccessKey().empty())
163 {
164 json.WithString("AccessKey", request->GetSigningAccessKey());
165 }
166
167 const auto& headers = outcome.IsSuccess() ? outcome.GetResult()->GetHeaders() : outcome.GetError().GetResponseHeaders();
168
169 ExportResponseHeaderToJson(json, headers, StringUtils::ToLower("x-amzn-RequestId"), "XAmznRequestId");
170 ExportResponseHeaderToJson(json, headers, StringUtils::ToLower("x-amz-request-id"), "XAmzRequestId");
171 ExportResponseHeaderToJson(json, headers, StringUtils::ToLower("x-amz-id-2"), "XAmzId2");
172
173 if (!outcome.IsSuccess())
174 {
175 if (outcome.GetError().GetExceptionName().empty()) // Not Aws Excecption
176 {
177 json.WithString("SdkExceptionMessage", outcome.GetError().GetMessage().substr(0, ERROR_MESSAGE_LENGTH_LIMIT));
178 }
179 else // Aws Exception
180 {
181 json.WithString("AwsException", outcome.GetError().GetExceptionName())
182 .WithString("AwsExceptionMessage", outcome.GetError().GetMessage().substr(0, ERROR_MESSAGE_LENGTH_LIMIT));
183 }
184 json.WithInteger("HttpStatusCode", static_cast<int>(outcome.GetError().GetResponseCode()));
185 }
186 else
187 {
188 json.WithInteger("HttpStatusCode", static_cast<int>(outcome.GetResult()->GetResponseCode()));
189 }
190
191 // Optional MetricsCollectedFromCore
192 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::AcquireConnectionLatency);
193 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::ConnectionReused);
194 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::ConnectLatency);
195 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::DestinationIp);
196 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::DnsLatency);
197 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::RequestLatency);
198 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::SslLatency);
199 ExportHttpMetricsToJson(json, metricsFromCore.httpClientMetrics, HttpClientMetricsType::TcpLatency);
200 }
201
202 DefaultMonitoring::DefaultMonitoring(const Aws::String& clientId, const Aws::String& host, unsigned short port):
203 m_udp(host.c_str(), port), m_clientId(clientId)
204 {
205 }
206
207 void* DefaultMonitoring::OnRequestStarted(const Aws::String& serviceName, const Aws::String& requestName, const std::shared_ptr<const Aws::Http::HttpRequest>& request) const
208 {
209 AWS_UNREFERENCED_PARAM(request);
210
211 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "OnRequestStart Service: " << serviceName << "Request: " << requestName);
212 auto context = Aws::New<DefaultContext>(DEFAULT_MONITORING_ALLOC_TAG);
213 context->apiCallStartTime = Aws::Utils::DateTime::Now();
214 context->attemptStartTime = context->apiCallStartTime;
215 context->retryCount = 0;
216 return context;
217 }
218
219
220 void DefaultMonitoring::OnRequestSucceeded(const Aws::String& serviceName, const Aws::String& requestName, const std::shared_ptr<const Aws::Http::HttpRequest>& request,
221 const Aws::Client::HttpResponseOutcome& outcome, const CoreMetricsCollection& metricsFromCore, void* context) const
222 {
223 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "OnRequestSucceeded Service: " << serviceName << "Request: " << requestName);
224 CollectAndSendAttemptData(serviceName, requestName, request, outcome, metricsFromCore, context);
225 }
226
227 void DefaultMonitoring::OnRequestFailed(const Aws::String& serviceName, const Aws::String& requestName, const std::shared_ptr<const Aws::Http::HttpRequest>& request,
228 const Aws::Client::HttpResponseOutcome& outcome, const CoreMetricsCollection& metricsFromCore, void* context) const
229 {
230 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "OnRequestFailed Service: " << serviceName << "Request: " << requestName);
231 CollectAndSendAttemptData(serviceName, requestName, request, outcome, metricsFromCore, context);
232 }
233
234 void DefaultMonitoring::OnRequestRetry(const Aws::String& serviceName, const Aws::String& requestName,
235 const std::shared_ptr<const Aws::Http::HttpRequest>& request, void* context) const
236 {
237 AWS_UNREFERENCED_PARAM(request);
238
239 DefaultContext* defaultContext = static_cast<DefaultContext*>(context);
240 defaultContext->retryCount++;
241 defaultContext->attemptStartTime = Aws::Utils::DateTime::Now();
242 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "OnRequestRetry Service: " << serviceName << "Request: " << requestName << " RetryCnt:" << defaultContext->retryCount);
243 }
244
245 void DefaultMonitoring::OnFinish(const Aws::String& serviceName, const Aws::String& requestName,
246 const std::shared_ptr<const Aws::Http::HttpRequest>& request, void* context) const
247 {
248 AWS_UNREFERENCED_PARAM(request);
249 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "OnRequestFinish Service: " << serviceName << "Request: " << requestName);
250
251 DefaultContext* defaultContext = static_cast<DefaultContext*>(context);
252 Aws::Utils::Json::JsonValue json;
253 FillRequiredFieldsToJson(json, "ApiCall", serviceName, requestName, m_clientId, defaultContext->apiCallStartTime, DEFAULT_MONITORING_VERSION, request->GetUserAgent());
254 FillRequiredApiCallFieldsToJson(json, defaultContext->retryCount + 1, (DateTime::Now() - defaultContext->apiCallStartTime).count(), (!defaultContext->lastAttemptSucceeded && defaultContext->lastErrorRetriable));
255 FillOptionalApiCallFieldsToJson(json, request.get(), *(defaultContext->outcome));
256 Aws::String compactData = json.View().WriteCompact();
257 m_udp.SendData(reinterpret_cast<const uint8_t*>(compactData.c_str()), static_cast<int>(compactData.size()));
258 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Send API Metrics: \n" << json.View().WriteReadable());
259 Aws::Delete(defaultContext);
260 }
261
262 void DefaultMonitoring::CollectAndSendAttemptData(const Aws::String& serviceName, const Aws::String& requestName,
263 const std::shared_ptr<const Aws::Http::HttpRequest>& request, const Aws::Client::HttpResponseOutcome& outcome,
264 const CoreMetricsCollection& metricsFromCore, void* context) const
265 {
266 DefaultContext* defaultContext = static_cast<DefaultContext*>(context);
267 defaultContext->outcome = &outcome;
268 defaultContext->lastAttemptSucceeded = outcome.IsSuccess() ? true : false;
269 defaultContext->lastErrorRetriable = (!outcome.IsSuccess() && outcome.GetError().ShouldRetry()) ? true : false;
270 Aws::Utils::Json::JsonValue json;
271 FillRequiredFieldsToJson(json, "ApiCallAttempt", serviceName, requestName, m_clientId, defaultContext->attemptStartTime, DEFAULT_MONITORING_VERSION, request->GetUserAgent());
272 FillRequiredApiAttemptFieldsToJson(json, request->GetUri().GetAuthority(), (DateTime::Now() - defaultContext->attemptStartTime).count());
273 FillOptionalApiAttemptFieldsToJson(json, request.get(), outcome, metricsFromCore);
274 Aws::String compactData = json.View().WriteCompact();
275 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Send Attempt Metrics: \n" << json.View().WriteReadable());
276 m_udp.SendData(reinterpret_cast<const uint8_t*>(compactData.c_str()), static_cast<int>(compactData.size()));
277 }
278
279 Aws::UniquePtr<MonitoringInterface> DefaultMonitoringFactory::CreateMonitoringInstance() const
280 {
281 Aws::String clientId(DEFAULT_MONITORING_CLIENT_ID); // default to empty
282 Aws::String host(DEFAULT_MONITORING_HOST); // default to 127.0.0.1
283 unsigned short port = DEFAULT_MONITORING_PORT; // default to 31000
284 bool enable = DEFAULT_MONITORING_ENABLE; //default to false;
285
286 //check profile_config
287 Aws::String tmpEnable = Aws::Config::GetCachedConfigValue(DefaultMonitoring::DEFAULT_CSM_CONFIG_ENABLED);
288 Aws::String tmpClientId = Aws::Config::GetCachedConfigValue(DefaultMonitoring::DEFAULT_CSM_CONFIG_CLIENT_ID);
289 Aws::String tmpHost = Aws::Config::GetCachedConfigValue(DefaultMonitoring::DEFAULT_CSM_CONFIG_HOST);
290 Aws::String tmpPort = Aws::Config::GetCachedConfigValue(DefaultMonitoring::DEFAULT_CSM_CONFIG_PORT);
291
292 if (!tmpEnable.empty())
293 {
294 enable = StringUtils::CaselessCompare(tmpEnable.c_str(), "true") ? true : false;
295 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved csm_enabled from profile_config to be " << enable);
296 }
297 if (!tmpClientId.empty())
298 {
299 clientId = tmpClientId;
300 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved csm_client_id from profile_config to be " << clientId);
301 }
302
303 if (!tmpHost.empty())
304 {
305 host = tmpHost;
306 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved csm_host from profile_config to be " << host);
307 }
308
309 if (!tmpPort.empty())
310 {
311 port = static_cast<short>(StringUtils::ConvertToInt32(tmpPort.c_str()));
312 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved csm_port from profile_config to be " << port);
313 }
314
315 // check environment variables
316 tmpEnable = Aws::Environment::GetEnv(DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_ENABLED);
317 tmpClientId = Aws::Environment::GetEnv(DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_CLIENT_ID);
318 tmpHost = Aws::Environment::GetEnv(DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_HOST);
319 tmpPort = Aws::Environment::GetEnv(DefaultMonitoring::DEFAULT_CSM_ENVIRONMENT_VAR_PORT);
320 if (!tmpEnable.empty())
321 {
322 enable = StringUtils::CaselessCompare(tmpEnable.c_str(), "true") ? true : false;
323 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved AWS_CSM_ENABLED from Environment variable to be " << enable);
324 }
325 if (!tmpClientId.empty())
326 {
327 clientId = tmpClientId;
328 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved AWS_CSM_CLIENT_ID from Environment variable to be " << clientId);
329
330 }
331 if (!tmpHost.empty())
332 {
333 host = tmpHost;
334 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved AWS_CSM_HOST from Environment variable to be " << host);
335 }
336 if (!tmpPort.empty())
337 {
338 port = static_cast<unsigned short>(StringUtils::ConvertToInt32(tmpPort.c_str()));
339 AWS_LOGSTREAM_DEBUG(DEFAULT_MONITORING_ALLOC_TAG, "Resolved AWS_CSM_PORT from Environment variable to be " << port);
340 }
341
342 if (!enable)
343 {
344 return nullptr;
345 }
346 return Aws::MakeUnique<DefaultMonitoring>(DEFAULT_MONITORING_ALLOC_TAG, clientId, host, port);
347 }
348
349 }
350}
351