[otlp] Add Grpc vendored code (#5981)

This commit is contained in:
Rajkumar Rangaraj 2024-11-18 11:25:50 -08:00 committed by GitHub
parent 913bccfdfc
commit 68c79a3bfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 457 additions and 0 deletions

View File

@ -29,3 +29,20 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License notice for gRPC for .NET (https://github.com/grpc/grpc-dotnet)
----------------------------------------------------------------------------------------------
Copyright 2019 The gRPC Authors
Licensed 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.

View File

@ -0,0 +1,144 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2019 The gRPC Authors
//
// Licensed 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.
using System.Diagnostics.CodeAnalysis;
#if NET462
using System.Net.Http;
#endif
using System.Net.Http.Headers;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
internal static class GrpcProtocolHelpers
{
internal const string StatusTrailer = "grpc-status";
internal const string MessageTrailer = "grpc-message";
internal const string CancelledDetail = "No grpc-status found on response.";
public static Status? GetResponseStatus(HttpHeaders trailingHeaders, HttpResponseMessage httpResponse)
{
Status? status;
try
{
var result = trailingHeaders.Any() ? TryGetStatusCore(trailingHeaders, out status) : TryGetStatusCore(httpResponse.Headers, out status);
if (!result)
{
status = new Status(StatusCode.Cancelled, CancelledDetail);
}
}
catch (Exception ex)
{
// Handle error from parsing badly formed status
status = new Status(StatusCode.Cancelled, ex.Message, ex);
}
return status;
}
public static bool TryGetStatusCore(HttpHeaders headers, [NotNullWhen(true)] out Status? status)
{
var grpcStatus = GetHeaderValue(headers, StatusTrailer);
// grpc-status is a required trailer
if (grpcStatus == null)
{
status = null;
return false;
}
int statusValue;
if (!int.TryParse(grpcStatus, out statusValue))
{
throw new InvalidOperationException("Unexpected grpc-status value: " + grpcStatus);
}
// grpc-message is optional
// Always read the gRPC message from the same headers collection as the status
var grpcMessage = GetHeaderValue(headers, MessageTrailer);
if (!string.IsNullOrEmpty(grpcMessage))
{
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses
// The value portion of Status-Message is conceptually a Unicode string description of the error,
// physically encoded as UTF-8 followed by percent-encoding.
grpcMessage = Uri.UnescapeDataString(grpcMessage);
}
status = new Status((StatusCode)statusValue, grpcMessage ?? string.Empty);
return true;
}
public static string? GetHeaderValue(HttpHeaders? headers, string name, bool first = false)
{
if (headers == null)
{
return null;
}
#if NET6_0_OR_GREATER
if (!headers.NonValidated.TryGetValues(name, out var values))
{
return null;
}
using (var e = values.GetEnumerator())
{
if (!e.MoveNext())
{
return null;
}
var result = e.Current;
if (!e.MoveNext())
{
return result;
}
if (first)
{
return result;
}
}
throw new InvalidOperationException($"Multiple {name} headers.");
#else
if (!headers.TryGetValues(name, out var values))
{
return null;
}
// HttpHeaders appears to always return an array, but fallback to converting values to one just in case
var valuesArray = values as string[] ?? values.ToArray();
switch (valuesArray.Length)
{
case 0:
return null;
case 1:
return valuesArray[0];
default:
if (first)
{
return valuesArray[0];
}
throw new InvalidOperationException($"Multiple {name} headers.");
}
#endif
}
}

View File

@ -0,0 +1,112 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 gRPC authors.
//
// Licensed 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.
using System.Diagnostics;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
/// <summary>
/// Represents RPC result, which consists of <see cref="StatusCode"/> and an optional detail string.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
internal struct Status
{
/// <summary>
/// Default result of a successful RPC. StatusCode=OK, empty details message.
/// </summary>
public static readonly Status DefaultSuccess = new Status(StatusCode.OK, string.Empty);
/// <summary>
/// Default result of a cancelled RPC. StatusCode=Cancelled, empty details message.
/// </summary>
public static readonly Status DefaultCancelled = new Status(StatusCode.Cancelled, string.Empty);
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> struct.
/// </summary>
/// <param name="statusCode">Status code.</param>
/// <param name="detail">Detail.</param>
public Status(StatusCode statusCode, string detail)
: this(statusCode, detail, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> struct.
/// Users should not use this constructor, except for creating instances for testing.
/// The debug error string should only be populated by gRPC internals.
/// Note: experimental API that can change or be removed without any prior notice.
/// </summary>
/// <param name="statusCode">Status code.</param>
/// <param name="detail">Detail.</param>
/// <param name="debugException">Optional internal error details.</param>
public Status(StatusCode statusCode, string detail, Exception? debugException)
{
this.StatusCode = statusCode;
this.Detail = detail;
this.DebugException = debugException;
}
/// <summary>
/// Gets the gRPC status code. OK indicates success, all other values indicate an error.
/// </summary>
public StatusCode StatusCode { get; }
/// <summary>
/// Gets the detail.
/// </summary>
public string Detail { get; }
/// <summary>
/// Gets in case of an error, this field may contain additional error details to help with debugging.
/// This field will be only populated on a client and its value is generated locally,
/// based on the internal state of the gRPC client stack (i.e. the value is never sent over the wire).
/// Note that this field is available only for debugging purposes, the application logic should
/// never rely on values of this field (it should use <c>StatusCode</c> and <c>Detail</c> instead).
/// Example: when a client fails to connect to a server, this field may provide additional details
/// why the connection to the server has failed.
/// Note: experimental API that can change or be removed without any prior notice.
/// </summary>
public Exception? DebugException { get; }
public override string ToString()
{
if (this.DebugException != null)
{
return $"Status(StatusCode=\"{this.StatusCode}\", Detail=\"{this.Detail}\"," +
$" DebugException=\"{this.DebugException.GetType()}: {this.DebugException.Message}\")";
}
return $"Status(StatusCode=\"{this.StatusCode}\", Detail=\"{this.Detail}\")";
}
private string DebuggerToString()
{
var text = $"StatusCode = {this.StatusCode}";
if (!string.IsNullOrEmpty(this.Detail))
{
text += $@", Detail = ""{this.Detail}""";
}
if (this.DebugException != null)
{
text += $@", DebugException = ""{this.DebugException.GetType()}: {this.DebugException.Message}""";
}
return text;
}
}

View File

@ -0,0 +1,123 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 gRPC authors.
//
// Licensed 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.
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
/// <summary>
/// Result of a remote procedure call.
/// Based on grpc_status_code from grpc/status.h.
/// </summary>
internal enum StatusCode
{
/// <summary>Not an error; returned on success.</summary>
OK = 0,
/// <summary>The operation was cancelled (typically by the caller).</summary>
Cancelled = 1,
/// <summary>
/// Unknown error. An example of where this error may be returned is
/// if a Status value received from another address space belongs to
/// an error-space that is not known in this address space. Also
/// errors raised by APIs that do not return enough error information
/// may be converted to this error.
/// </summary>
Unknown = 2,
/// <summary>
/// Client specified an invalid argument. Note that this differs
/// from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
/// that are problematic regardless of the state of the system
/// (e.g., a malformed file name).
/// </summary>
InvalidArgument = 3,
/// <summary>
/// Deadline expired before operation could complete. For operations
/// that change the state of the system, this error may be returned
/// even if the operation has completed successfully. For example, a
/// successful response from a server could have been delayed long
/// enough for the deadline to expire.
/// </summary>
DeadlineExceeded = 4,
/// <summary>Some requested entity (e.g., file or directory) was not found.</summary>
NotFound = 5,
/// <summary>Some entity that we attempted to create (e.g., file or directory) already exists.</summary>
AlreadyExists = 6,
/// <summary>
/// The caller does not have permission to execute the specified
/// operation. PERMISSION_DENIED must not be used for rejections
/// caused by exhausting some resource (use RESOURCE_EXHAUSTED
/// instead for those errors). PERMISSION_DENIED must not be
/// used if the caller can not be identified (use UNAUTHENTICATED
/// instead for those errors).
/// </summary>
PermissionDenied = 7,
/// <summary>The request does not have valid authentication credentials for the operation.</summary>
Unauthenticated = 16,
/// <summary>
/// Some resource has been exhausted, perhaps a per-user quota, or
/// perhaps the entire file system is out of space.
/// </summary>
ResourceExhausted = 8,
/// <summary>
/// Operation was rejected because the system is not in a state
/// required for the operation's execution. For example, directory
/// to be deleted may be non-empty, an rmdir operation is applied to
/// a non-directory, etc.
/// </summary>
FailedPrecondition = 9,
/// <summary>
/// The operation was aborted, typically due to a concurrency issue
/// like sequencer check failures, transaction aborts, etc.
/// </summary>
Aborted = 10,
/// <summary>
/// Operation was attempted past the valid range. E.g., seeking or
/// reading past end of file.
/// </summary>
OutOfRange = 11,
/// <summary>Operation is not implemented or not supported/enabled in this service.</summary>
Unimplemented = 12,
/// <summary>
/// Internal errors. Means some invariants expected by underlying
/// system has been broken. If you see one of these errors,
/// something is very broken.
/// </summary>
Internal = 13,
/// <summary>
/// The service is currently unavailable. This is a most likely a
/// transient condition and may be corrected by retrying with
/// a backoff. Note that it is not always safe to retry
/// non-idempotent operations.
/// </summary>
Unavailable = 14,
/// <summary>Unrecoverable data loss or corruption.</summary>
DataLoss = 15,
}

View File

@ -0,0 +1,61 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2019 The gRPC Authors
//
// Licensed 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.
#if NET462
using System.Net.Http;
#endif
using System.Net.Http.Headers;
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
internal static class TrailingHeadersHelpers
{
public static readonly string ResponseTrailersKey = "__ResponseTrailers";
public static HttpHeaders TrailingHeaders(this HttpResponseMessage responseMessage)
{
#if !NETSTANDARD2_0 && !NET462
return responseMessage.TrailingHeaders;
#else
if (responseMessage.RequestMessage.Properties.TryGetValue(ResponseTrailersKey, out var headers) &&
headers is HttpHeaders httpHeaders)
{
return httpHeaders;
}
// App targets .NET Standard 2.0 and the handler hasn't set trailers
// in RequestMessage.Properties with known key. Return empty collection.
// Client call will likely fail because it is unable to get a grpc-status.
return ResponseTrailers.Empty;
#endif
}
#if NETSTANDARD2_0 || NET462
public static void EnsureTrailingHeaders(this HttpResponseMessage responseMessage)
{
if (!responseMessage.RequestMessage.Properties.ContainsKey(ResponseTrailersKey))
{
responseMessage.RequestMessage.Properties[ResponseTrailersKey] = new ResponseTrailers();
}
}
private class ResponseTrailers : HttpHeaders
{
public static readonly ResponseTrailers Empty = new ResponseTrailers();
}
#endif
}