Skip to content

Commit 68c79a3

Browse files
[otlp] Add Grpc vendored code (open-telemetry#5981)
1 parent 913bccf commit 68c79a3

File tree

5 files changed

+457
-0
lines changed

5 files changed

+457
-0
lines changed

THIRD-PARTY-NOTICES.TXT

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,20 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2929
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3030
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3131
SOFTWARE.
32+
33+
License notice for gRPC for .NET (https://github.com/grpc/grpc-dotnet)
34+
----------------------------------------------------------------------------------------------
35+
36+
Copyright 2019 The gRPC Authors
37+
38+
Licensed under the Apache License, Version 2.0 (the "License");
39+
you may not use this file except in compliance with the License.
40+
You may obtain a copy of the License at
41+
42+
http://www.apache.org/licenses/LICENSE-2.0
43+
44+
Unless required by applicable law or agreed to in writing, software
45+
distributed under the License is distributed on an "AS IS" BASIS,
46+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
47+
See the License for the specific language governing permissions and
48+
limitations under the License.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Copyright 2019 The gRPC Authors
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
using System.Diagnostics.CodeAnalysis;
19+
#if NET462
20+
using System.Net.Http;
21+
#endif
22+
using System.Net.Http.Headers;
23+
24+
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
25+
26+
internal static class GrpcProtocolHelpers
27+
{
28+
internal const string StatusTrailer = "grpc-status";
29+
internal const string MessageTrailer = "grpc-message";
30+
internal const string CancelledDetail = "No grpc-status found on response.";
31+
32+
public static Status? GetResponseStatus(HttpHeaders trailingHeaders, HttpResponseMessage httpResponse)
33+
{
34+
Status? status;
35+
try
36+
{
37+
var result = trailingHeaders.Any() ? TryGetStatusCore(trailingHeaders, out status) : TryGetStatusCore(httpResponse.Headers, out status);
38+
39+
if (!result)
40+
{
41+
status = new Status(StatusCode.Cancelled, CancelledDetail);
42+
}
43+
}
44+
catch (Exception ex)
45+
{
46+
// Handle error from parsing badly formed status
47+
status = new Status(StatusCode.Cancelled, ex.Message, ex);
48+
}
49+
50+
return status;
51+
}
52+
53+
public static bool TryGetStatusCore(HttpHeaders headers, [NotNullWhen(true)] out Status? status)
54+
{
55+
var grpcStatus = GetHeaderValue(headers, StatusTrailer);
56+
57+
// grpc-status is a required trailer
58+
if (grpcStatus == null)
59+
{
60+
status = null;
61+
return false;
62+
}
63+
64+
int statusValue;
65+
if (!int.TryParse(grpcStatus, out statusValue))
66+
{
67+
throw new InvalidOperationException("Unexpected grpc-status value: " + grpcStatus);
68+
}
69+
70+
// grpc-message is optional
71+
// Always read the gRPC message from the same headers collection as the status
72+
var grpcMessage = GetHeaderValue(headers, MessageTrailer);
73+
74+
if (!string.IsNullOrEmpty(grpcMessage))
75+
{
76+
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses
77+
// The value portion of Status-Message is conceptually a Unicode string description of the error,
78+
// physically encoded as UTF-8 followed by percent-encoding.
79+
grpcMessage = Uri.UnescapeDataString(grpcMessage);
80+
}
81+
82+
status = new Status((StatusCode)statusValue, grpcMessage ?? string.Empty);
83+
return true;
84+
}
85+
86+
public static string? GetHeaderValue(HttpHeaders? headers, string name, bool first = false)
87+
{
88+
if (headers == null)
89+
{
90+
return null;
91+
}
92+
93+
#if NET6_0_OR_GREATER
94+
if (!headers.NonValidated.TryGetValues(name, out var values))
95+
{
96+
return null;
97+
}
98+
99+
using (var e = values.GetEnumerator())
100+
{
101+
if (!e.MoveNext())
102+
{
103+
return null;
104+
}
105+
106+
var result = e.Current;
107+
if (!e.MoveNext())
108+
{
109+
return result;
110+
}
111+
112+
if (first)
113+
{
114+
return result;
115+
}
116+
}
117+
118+
throw new InvalidOperationException($"Multiple {name} headers.");
119+
#else
120+
if (!headers.TryGetValues(name, out var values))
121+
{
122+
return null;
123+
}
124+
125+
// HttpHeaders appears to always return an array, but fallback to converting values to one just in case
126+
var valuesArray = values as string[] ?? values.ToArray();
127+
128+
switch (valuesArray.Length)
129+
{
130+
case 0:
131+
return null;
132+
case 1:
133+
return valuesArray[0];
134+
default:
135+
if (first)
136+
{
137+
return valuesArray[0];
138+
}
139+
140+
throw new InvalidOperationException($"Multiple {name} headers.");
141+
}
142+
#endif
143+
}
144+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Copyright 2015 gRPC authors.
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
using System.Diagnostics;
19+
20+
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
21+
22+
/// <summary>
23+
/// Represents RPC result, which consists of <see cref="StatusCode"/> and an optional detail string.
24+
/// </summary>
25+
[DebuggerDisplay("{DebuggerToString(),nq}")]
26+
internal struct Status
27+
{
28+
/// <summary>
29+
/// Default result of a successful RPC. StatusCode=OK, empty details message.
30+
/// </summary>
31+
public static readonly Status DefaultSuccess = new Status(StatusCode.OK, string.Empty);
32+
33+
/// <summary>
34+
/// Default result of a cancelled RPC. StatusCode=Cancelled, empty details message.
35+
/// </summary>
36+
public static readonly Status DefaultCancelled = new Status(StatusCode.Cancelled, string.Empty);
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="Status"/> struct.
40+
/// </summary>
41+
/// <param name="statusCode">Status code.</param>
42+
/// <param name="detail">Detail.</param>
43+
public Status(StatusCode statusCode, string detail)
44+
: this(statusCode, detail, null)
45+
{
46+
}
47+
48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="Status"/> struct.
50+
/// Users should not use this constructor, except for creating instances for testing.
51+
/// The debug error string should only be populated by gRPC internals.
52+
/// Note: experimental API that can change or be removed without any prior notice.
53+
/// </summary>
54+
/// <param name="statusCode">Status code.</param>
55+
/// <param name="detail">Detail.</param>
56+
/// <param name="debugException">Optional internal error details.</param>
57+
public Status(StatusCode statusCode, string detail, Exception? debugException)
58+
{
59+
this.StatusCode = statusCode;
60+
this.Detail = detail;
61+
this.DebugException = debugException;
62+
}
63+
64+
/// <summary>
65+
/// Gets the gRPC status code. OK indicates success, all other values indicate an error.
66+
/// </summary>
67+
public StatusCode StatusCode { get; }
68+
69+
/// <summary>
70+
/// Gets the detail.
71+
/// </summary>
72+
public string Detail { get; }
73+
74+
/// <summary>
75+
/// Gets in case of an error, this field may contain additional error details to help with debugging.
76+
/// This field will be only populated on a client and its value is generated locally,
77+
/// based on the internal state of the gRPC client stack (i.e. the value is never sent over the wire).
78+
/// Note that this field is available only for debugging purposes, the application logic should
79+
/// never rely on values of this field (it should use <c>StatusCode</c> and <c>Detail</c> instead).
80+
/// Example: when a client fails to connect to a server, this field may provide additional details
81+
/// why the connection to the server has failed.
82+
/// Note: experimental API that can change or be removed without any prior notice.
83+
/// </summary>
84+
public Exception? DebugException { get; }
85+
86+
public override string ToString()
87+
{
88+
if (this.DebugException != null)
89+
{
90+
return $"Status(StatusCode=\"{this.StatusCode}\", Detail=\"{this.Detail}\"," +
91+
$" DebugException=\"{this.DebugException.GetType()}: {this.DebugException.Message}\")";
92+
}
93+
94+
return $"Status(StatusCode=\"{this.StatusCode}\", Detail=\"{this.Detail}\")";
95+
}
96+
97+
private string DebuggerToString()
98+
{
99+
var text = $"StatusCode = {this.StatusCode}";
100+
if (!string.IsNullOrEmpty(this.Detail))
101+
{
102+
text += $@", Detail = ""{this.Detail}""";
103+
}
104+
105+
if (this.DebugException != null)
106+
{
107+
text += $@", DebugException = ""{this.DebugException.GetType()}: {this.DebugException.Message}""";
108+
}
109+
110+
return text;
111+
}
112+
}

0 commit comments

Comments
 (0)