├── SignKey.snk ├── test ├── Directory.Build.props └── OpenTracing.Contrib.Grpc.Test │ ├── protos │ └── addressbook.proto │ ├── OpenTracing.Contrib.Grpc.Test.csproj │ └── Program.cs ├── getting_started ├── README.md ├── jaeger-ui-1.png ├── jaeger-ui-2.png ├── jaeger-ui-3.png ├── GreeterClient │ ├── GreeterClient.csproj │ └── Program.cs ├── GreeterServer │ ├── GreeterServer.csproj │ └── Program.cs ├── GreeterShared │ ├── GreeterShared.csproj │ ├── HelloworldGrpc.cs │ └── Helloworld.cs ├── generate_protos.bat ├── protos │ └── helloworld.proto └── Greeter.sln ├── images └── opentracing-icon.png ├── NuGet.config ├── src ├── OpenTracing.Contrib.Grpc │ ├── OperationNameConstructor │ │ ├── IOperationNameConstructor.cs │ │ ├── DefaultOperationNameConstructor.cs │ │ └── PrefixOperationNameConstructor.cs │ ├── OpenTracing.Contrib.Grpc.csproj │ ├── Constants.cs │ ├── Streaming │ │ ├── ScopeActions.cs │ │ ├── TracingServerStreamWriter.cs │ │ ├── StreamActions.cs │ │ ├── TracingClientStreamWriter.cs │ │ └── TracingAsyncStreamReader.cs │ ├── Configuration │ │ ├── TracingConfiguration.cs │ │ ├── ServerTracingConfiguration.cs │ │ └── ClientTracingConfiguration.cs │ ├── Propagation │ │ └── MetadataCarrier.cs │ ├── Extensions.cs │ ├── GrpcTraceLogger.cs │ ├── Interceptors │ │ ├── ServerTracingInterceptor.cs │ │ └── ClientTracingInterceptor.cs │ └── Handler │ │ ├── InterceptedServerHandler.cs │ │ └── InterceptedClientHandler.cs └── Directory.Build.props ├── Directory.Build.props ├── .appveyor.yml ├── version.props ├── OpenTracing.Contrib.sln ├── .gitignore ├── README.md └── LICENSE /SignKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/SignKey.snk -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /getting_started/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/getting_started/README.md -------------------------------------------------------------------------------- /images/opentracing-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/images/opentracing-icon.png -------------------------------------------------------------------------------- /getting_started/jaeger-ui-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/getting_started/jaeger-ui-1.png -------------------------------------------------------------------------------- /getting_started/jaeger-ui-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/getting_started/jaeger-ui-2.png -------------------------------------------------------------------------------- /getting_started/jaeger-ui-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opentracing-contrib/csharp-grpc/HEAD/getting_started/jaeger-ui-3.png -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/OperationNameConstructor/IOperationNameConstructor.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | 3 | namespace OpenTracing.Contrib.Grpc.OperationNameConstructor 4 | { 5 | public interface IOperationNameConstructor 6 | { 7 | string ConstructOperationName(Method method); 8 | string ConstructOperationName(string method); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /getting_started/GreeterClient/GreeterClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GreeterClient 5 | Exe 6 | netcoreapp2.0 7 | GreeterClient 8 | GreeterClient 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /getting_started/GreeterServer/GreeterServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GreeterServer 5 | Exe 6 | netcoreapp2.0 7 | GreeterServer 8 | GreeterServer 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/OperationNameConstructor/DefaultOperationNameConstructor.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | 3 | namespace OpenTracing.Contrib.Grpc.OperationNameConstructor 4 | { 5 | public class DefaultOperationNameConstructor : IOperationNameConstructor 6 | { 7 | public string ConstructOperationName(Method method) 8 | { 9 | return method.FullName; 10 | } 11 | 12 | public string ConstructOperationName(string method) 13 | { 14 | return method; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/OpenTracing.Contrib.Grpc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net462;netstandard2.1 5 | true 6 | Adds OpenTracing instrumentation for .NET Standard apps that use GRPC. 7 | opentracing;distributed-tracing;tracing;grpc;netstandard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /getting_started/GreeterShared/GreeterShared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Greeter 5 | netcoreapp2.0 6 | GreeterShared 7 | Greeter 8 | GreeterShared 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace OpenTracing.Contrib.Grpc 2 | { 3 | public static class Constants 4 | { 5 | public const string TAGS_COMPONENT = "grpc"; 6 | 7 | public const string TAGS_GRPC_AUTHORITY = "grpc.authority"; 8 | public const string TAGS_GRPC_CALL_OPTIONS = "grpc.call_options"; 9 | public const string TAGS_GRPC_DEADLINE_MILLIS = "grpc.deadline_millis"; 10 | public const string TAGS_GRPC_HEADERS = "grpc.headers"; 11 | public const string TAGS_GRPC_METHOD_NAME = "grpc.method_name"; 12 | public const string TAGS_GRPC_METHOD_TYPE = "grpc.method_type"; 13 | 14 | public const string TAGS_PEER_ADDRESS = "peer.address"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/OperationNameConstructor/PrefixOperationNameConstructor.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | 3 | namespace OpenTracing.Contrib.Grpc.OperationNameConstructor 4 | { 5 | public class PrefixOperationNameConstructor : IOperationNameConstructor 6 | { 7 | private readonly string _prefix; 8 | 9 | public PrefixOperationNameConstructor(string prefix) 10 | { 11 | _prefix = prefix; 12 | } 13 | 14 | public string ConstructOperationName(Method method) 15 | { 16 | return $"{_prefix} {method.FullName}"; 17 | } 18 | 19 | public string ConstructOperationName(string method) 20 | { 21 | return $"{_prefix} {method}"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Streaming/ScopeActions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenTracing.Contrib.Grpc.Streaming 4 | { 5 | internal readonly struct ScopeActions 6 | { 7 | public string ScopeOperationName { get; } 8 | public Action OnBeginScope { get; } 9 | public Action OnEndScope { get; } 10 | 11 | public ScopeActions(string scopeOperationName, Action onBeginScope, Action onEndScope) 12 | { 13 | ScopeOperationName = scopeOperationName; 14 | OnBeginScope = onBeginScope; 15 | OnEndScope = onEndScope; 16 | } 17 | 18 | public void BeginScope() 19 | { 20 | OnBeginScope?.Invoke(ScopeOperationName); 21 | } 22 | 23 | public void EndScope() 24 | { 25 | OnEndScope?.Invoke(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.Grpc.Test/protos/addressbook.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package tutorial; 4 | 5 | message Person { 6 | string name = 1; 7 | int32 id = 2; // Unique ID number for this person. 8 | string email = 3; 9 | 10 | enum PhoneType { 11 | MOBILE = 0; 12 | HOME = 1; 13 | WORK = 2; 14 | } 15 | 16 | message PhoneNumber { 17 | string number = 1; 18 | PhoneType type = 2; 19 | } 20 | 21 | repeated PhoneNumber phones = 4; 22 | } 23 | 24 | // Our address book file is just one of these. 25 | message AddressBook { 26 | repeated Person people = 1; 27 | } 28 | 29 | service Phone { 30 | rpc GetName(Person) returns (Person) {} 31 | rpc GetNameRequestStream(stream Person) returns (Person) {} 32 | rpc GetNameResponseStream(Person) returns (stream Person) {} 33 | rpc GetNameBiDiStream(stream Person) returns (stream Person) {} 34 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(NoWarn);CS1591 6 | true 7 | true 8 | $(MSBuildThisFileDirectory)..\SignKey.snk 9 | true 10 | 11 | 12 | portable 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.Grpc.Test/OpenTracing.Contrib.Grpc.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net462;netstandard2.1 6 | OpenTracing.Contrib.Grpc.Test 7 | OpenTracing.Contrib.Grpc.Test 8 | 7.3 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Benjamin Krämer 5 | opentracing-icon.png 6 | https://github.com/opentracing-contrib/csharp-grpc 7 | Apache-2.0 8 | https://github.com/opentracing-contrib/csharp-grpc/releases/tag/v$(Version) 9 | git 10 | git://github.com/opentracing-contrib/csharp-grpc 11 | 12 | 13 | false 14 | 15 | latest 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor Build number is incremental and not related to actual version number of the product 2 | version: '{build}' 3 | 4 | image: Visual Studio 2019 5 | 6 | init: 7 | - cmd: git config --global core.autocrlf true 8 | 9 | environment: 10 | global: 11 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 12 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 13 | 14 | build_script: 15 | - ps: .\build.ps1 16 | 17 | test: off 18 | 19 | artifacts: 20 | - path: artifacts\nuget\*.nupkg 21 | name: NuGet 22 | 23 | # Deploy every successful build (except PRs) to development feed 24 | nuget: 25 | account_feed: true 26 | project_feed: true 27 | disable_publish_on_pr: true 28 | 29 | deploy: 30 | # Create a GitHub release for every tag 31 | - provider: GitHub 32 | release: $(appveyor_repo_tag_name) 33 | description: "See milestone for changes: https://github.com/opentracing-contrib/csharp-grpc/milestones" 34 | draft: true 35 | auth_token: 36 | secure: jvG4e9KD/edT4snc8G0ayLpzJYUprnYwnwnvHwcJhzjSS1R3Dx216QORs5NMhQDP 37 | on: 38 | appveyor_repo_tag: true 39 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Streaming/TracingServerStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Grpc.Core; 3 | 4 | namespace OpenTracing.Contrib.Grpc.Streaming 5 | { 6 | internal class TracingServerStreamWriter : IServerStreamWriter 7 | { 8 | private readonly IServerStreamWriter _writer; 9 | private readonly StreamActions _streamActions; 10 | 11 | public TracingServerStreamWriter(IServerStreamWriter writer, StreamActions streamActions) 12 | { 13 | _writer = writer; 14 | _streamActions = streamActions; 15 | } 16 | 17 | public WriteOptions WriteOptions 18 | { 19 | get => _writer.WriteOptions; 20 | set => _writer.WriteOptions = value; 21 | } 22 | 23 | public Task WriteAsync(T message) 24 | { 25 | _streamActions.ScopeActions.EndScope(); 26 | _streamActions.ScopeActions.BeginScope(); 27 | _streamActions.Message(message); 28 | 29 | return _writer.WriteAsync(message); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Streaming/StreamActions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OpenTracing.Contrib.Grpc.Streaming 4 | { 5 | internal readonly struct StreamActions 6 | { 7 | public ScopeActions ScopeActions { get; } 8 | public Action OnMessage { get; } 9 | public Action OnStreamEnd { get; } 10 | public Action OnException { get; } 11 | 12 | public StreamActions(ScopeActions scopeActions, Action onMessage, Action onStreamEnd = null, Action onException = null) 13 | { 14 | ScopeActions = scopeActions; 15 | OnMessage = onMessage; 16 | OnStreamEnd = onStreamEnd; 17 | OnException = onException; 18 | } 19 | 20 | public void Message(T msg) 21 | { 22 | OnMessage?.Invoke(msg); 23 | } 24 | 25 | public void Exception(Exception ex) 26 | { 27 | OnException?.Invoke(ex); 28 | } 29 | 30 | public void StreamEnd() 31 | { 32 | OnStreamEnd?.Invoke(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Configuration/TracingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using OpenTracing.Contrib.Grpc.OperationNameConstructor; 2 | 3 | namespace OpenTracing.Contrib.Grpc.Configuration 4 | { 5 | public abstract class TracingConfiguration 6 | { 7 | public ITracer Tracer { get; } 8 | public IOperationNameConstructor OperationNameConstructor { get; } 9 | public bool Streaming { get; } 10 | public bool StreamingInputSpans { get; } 11 | public bool StreamingOutputSpans { get; } 12 | public bool Verbose { get; } 13 | 14 | protected TracingConfiguration(ITracer tracer, IOperationNameConstructor operationNameConstructor = null, bool streaming = false, bool streamingInputSpans = false, bool streamingOutputSpans = false, bool verbose = false) 15 | { 16 | Tracer = tracer; 17 | OperationNameConstructor = operationNameConstructor ?? new DefaultOperationNameConstructor(); 18 | Streaming = streaming; 19 | StreamingInputSpans = streamingInputSpans; 20 | StreamingOutputSpans = streamingOutputSpans; 21 | Verbose = verbose; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /getting_started/generate_protos.bat: -------------------------------------------------------------------------------- 1 | @rem Copyright 2016 gRPC authors. 2 | @rem 3 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 4 | @rem you may not use this file except in compliance with the License. 5 | @rem You may obtain a copy of the License at 6 | @rem 7 | @rem http://www.apache.org/licenses/LICENSE-2.0 8 | @rem 9 | @rem Unless required by applicable law or agreed to in writing, software 10 | @rem distributed under the License is distributed on an "AS IS" BASIS, 11 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @rem See the License for the specific language governing permissions and 13 | @rem limitations under the License. 14 | 15 | @rem Generate the C# code for .proto files 16 | 17 | @echo off 18 | setlocal 19 | 20 | @rem enter this directory 21 | cd /d %~dp0 22 | 23 | set PROTOC=%UserProfile%\.nuget\packages\Google.Protobuf.Tools\3.5.0\tools\windows_x64\protoc.exe 24 | set PLUGIN=%UserProfile%\.nuget\packages\Grpc.Tools\1.12.0\tools\windows_x64\grpc_csharp_plugin.exe 25 | 26 | %PROTOC% -Iprotos --csharp_out GreeterShared protos/helloworld.proto --grpc_out GreeterShared --plugin=protoc-gen-grpc=%PLUGIN% 27 | 28 | endlocal 29 | -------------------------------------------------------------------------------- /getting_started/protos/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 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 | // http://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 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | option objc_class_prefix = "HLW"; 21 | 22 | package helloworld; 23 | 24 | // The greeting service definition. 25 | service Greeter { 26 | // Sends a greeting 27 | rpc SayHello (HelloRequest) returns (HelloReply) {} 28 | } 29 | 30 | // The request message containing the user's name. 31 | message HelloRequest { 32 | string name = 1; 33 | } 34 | 35 | // The response message containing the greetings 36 | message HelloReply { 37 | string message = 1; 38 | } 39 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Streaming/TracingClientStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Grpc.Core; 3 | 4 | namespace OpenTracing.Contrib.Grpc.Streaming 5 | { 6 | internal class TracingClientStreamWriter : IClientStreamWriter 7 | { 8 | private readonly IClientStreamWriter _writer; 9 | private readonly StreamActions _streamActions; 10 | 11 | public TracingClientStreamWriter(IClientStreamWriter writer, StreamActions streamActions) 12 | { 13 | _writer = writer; 14 | _streamActions = streamActions; 15 | } 16 | 17 | public WriteOptions WriteOptions 18 | { 19 | get => _writer.WriteOptions; 20 | set => _writer.WriteOptions = value; 21 | } 22 | 23 | public Task WriteAsync(T message) 24 | { 25 | _streamActions.ScopeActions.EndScope(); 26 | _streamActions.ScopeActions.BeginScope(); 27 | _streamActions.Message(message); 28 | 29 | return _writer.WriteAsync(message); 30 | } 31 | 32 | public Task CompleteAsync() 33 | { 34 | _streamActions.ScopeActions.EndScope(); 35 | _streamActions.StreamEnd(); 36 | 37 | return _writer.CompleteAsync(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Configuration/ServerTracingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using OpenTracing.Contrib.Grpc.OperationNameConstructor; 3 | 4 | namespace OpenTracing.Contrib.Grpc.Configuration 5 | { 6 | public sealed class ServerTracingConfiguration : TracingConfiguration 7 | { 8 | public enum RequestAttribute 9 | { 10 | Headers, 11 | //MethodType, // TODO: Currently not supported by grpc-csharp 12 | MethodName, 13 | //CallAttributes, // TODO: Currently not supported by grpc-csharp 14 | } 15 | 16 | public ISet TracedAttributes { get; } 17 | 18 | internal ServerTracingConfiguration(ITracer tracer) : base(tracer) 19 | { 20 | TracedAttributes = new HashSet(); 21 | } 22 | 23 | internal ServerTracingConfiguration(ITracer tracer, IOperationNameConstructor operationNameConstructor, bool streaming, bool streamingInputSpans, bool streamingOutputSpans, bool verbose, ISet tracedAttributes) 24 | : base(tracer, operationNameConstructor, streaming, streamingInputSpans, streamingOutputSpans, verbose) 25 | { 26 | TracedAttributes = tracedAttributes ?? new HashSet(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /getting_started/GreeterClient/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 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 | // http://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 | using System; 16 | using Grpc.Core; 17 | using Helloworld; 18 | 19 | namespace GreeterClient 20 | { 21 | class Program 22 | { 23 | public static void Main(string[] args) 24 | { 25 | Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure); 26 | 27 | var client = new Greeter.GreeterClient(channel); 28 | String user = "you"; 29 | 30 | var reply = client.SayHello(new HelloRequest { Name = user }); 31 | Console.WriteLine("Greeting: " + reply.Message); 32 | 33 | channel.ShutdownAsync().Wait(); 34 | Console.WriteLine("Press any key to exit..."); 35 | Console.ReadKey(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 99.99.99 17 | 18 | 19 | loc$([System.DateTime]::UtcNow.ToString('yyyyMMddHHmm')) 20 | 21 | 22 | ci$([System.Int32]::Parse($(APPVEYOR_BUILD_NUMBER)).ToString('D4')) 23 | 24 | 25 | $(APPVEYOR_REPO_TAG_NAME.TrimStart('v')) 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Propagation/MetadataCarrier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using Grpc.Core; 4 | using OpenTracing.Propagation; 5 | 6 | namespace OpenTracing.Contrib.Grpc.Propagation 7 | { 8 | internal class MetadataCarrier : ITextMap 9 | { 10 | private readonly Metadata _metadata; 11 | 12 | public MetadataCarrier(Metadata metadata) 13 | { 14 | _metadata = metadata; 15 | } 16 | 17 | public void Set(string key, string value) 18 | { 19 | // We remove all the existing values for that key before adding the new one to have a "set" behavior: 20 | for (var i = _metadata.Count - 1; i >= 0; i--) 21 | { 22 | if (_metadata[i].Key == key) 23 | { 24 | _metadata.RemoveAt(i); 25 | } 26 | } 27 | 28 | _metadata.Add(key, value); 29 | } 30 | 31 | public IEnumerator> GetEnumerator() 32 | { 33 | foreach (var entry in _metadata) 34 | { 35 | if (entry.IsBinary) 36 | continue; 37 | 38 | yield return new KeyValuePair(entry.Key, entry.Value); 39 | } 40 | } 41 | 42 | IEnumerator IEnumerable.GetEnumerator() 43 | { 44 | return GetEnumerator(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Streaming/TracingAsyncStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Grpc.Core; 5 | 6 | namespace OpenTracing.Contrib.Grpc.Streaming 7 | { 8 | internal class TracingAsyncStreamReader : IAsyncStreamReader 9 | { 10 | private readonly IAsyncStreamReader _reader; 11 | private readonly StreamActions _streamActions; 12 | 13 | public T Current => _reader.Current; 14 | 15 | public TracingAsyncStreamReader(IAsyncStreamReader reader, StreamActions streamActions) 16 | { 17 | _reader = reader; 18 | _streamActions = streamActions; 19 | } 20 | 21 | public async Task MoveNext(CancellationToken cancellationToken) 22 | { 23 | try 24 | { 25 | _streamActions.ScopeActions.EndScope(); 26 | 27 | var hasNext = await _reader.MoveNext(cancellationToken).ConfigureAwait(false); 28 | if (hasNext) 29 | { 30 | _streamActions.ScopeActions.BeginScope(); 31 | _streamActions.Message(Current); 32 | } 33 | else 34 | { 35 | _streamActions.StreamEnd(); 36 | } 37 | 38 | return hasNext; 39 | } 40 | catch (Exception ex) 41 | { 42 | _streamActions.Exception(ex); 43 | throw; 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Configuration/ClientTracingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using OpenTracing.Contrib.Grpc.OperationNameConstructor; 4 | 5 | namespace OpenTracing.Contrib.Grpc.Configuration 6 | { 7 | public class ClientTracingConfiguration : TracingConfiguration 8 | { 9 | public enum RequestAttribute 10 | { 11 | MethodType, 12 | MethodName, 13 | Deadline, 14 | //Compressor, // TODO: Currently not supported by grpc-csharp 15 | Authority, 16 | AllCallOptions, 17 | Headers 18 | } 19 | 20 | public ISet TracedAttributes { get; } 21 | public bool WaitForReady { get; } 22 | public CancellationToken FallbackCancellationToken { get; } 23 | 24 | internal ClientTracingConfiguration(ITracer tracer) : base(tracer) 25 | { 26 | TracedAttributes = new HashSet(); 27 | } 28 | 29 | internal ClientTracingConfiguration(ITracer tracer, IOperationNameConstructor operationNameConstructor, bool streaming, bool streamingInputSpans, bool streamingOutputSpans, bool verbose, ISet tracedAttributes, bool waitForReady, CancellationToken fallbackCancellationToken) 30 | : base(tracer, operationNameConstructor, streaming, streamingInputSpans, streamingOutputSpans, verbose) 31 | { 32 | TracedAttributes = tracedAttributes ?? new HashSet(); 33 | WaitForReady = waitForReady; 34 | FallbackCancellationToken = fallbackCancellationToken; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /getting_started/GreeterServer/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 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 | // http://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 | using System; 16 | using System.Threading.Tasks; 17 | using Grpc.Core; 18 | using Helloworld; 19 | 20 | namespace GreeterServer 21 | { 22 | class GreeterImpl : Greeter.GreeterBase 23 | { 24 | // Server side handler of the SayHello RPC 25 | public override Task SayHello(HelloRequest request, ServerCallContext context) 26 | { 27 | return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); 28 | } 29 | } 30 | 31 | class Program 32 | { 33 | const int Port = 50051; 34 | 35 | public static void Main(string[] args) 36 | { 37 | Server server = new Server 38 | { 39 | Services = { Greeter.BindService(new GreeterImpl()) }, 40 | Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } 41 | }; 42 | server.Start(); 43 | 44 | Console.WriteLine("Greeter server listening on port " + Port); 45 | Console.WriteLine("Press any key to stop the server..."); 46 | Console.ReadKey(); 47 | 48 | server.ShutdownAsync().Wait(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /getting_started/Greeter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2015 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreeterShared", "GreeterShared\GreeterShared.csproj", "{15F841B4-51D8-474B-9786-D4DAFB16FC9B}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreeterClient", "GreeterClient\GreeterClient.csproj", "{D3DB087F-9AF3-4822-810D-8EA2A5F5F2B3}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreeterServer", "GreeterServer\GreeterServer.csproj", "{2A833B3F-CB43-4A31-A37F-8823C70740F1}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {15F841B4-51D8-474B-9786-D4DAFB16FC9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {15F841B4-51D8-474B-9786-D4DAFB16FC9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {15F841B4-51D8-474B-9786-D4DAFB16FC9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {15F841B4-51D8-474B-9786-D4DAFB16FC9B}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {D3DB087F-9AF3-4822-810D-8EA2A5F5F2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {D3DB087F-9AF3-4822-810D-8EA2A5F5F2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {D3DB087F-9AF3-4822-810D-8EA2A5F5F2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {D3DB087F-9AF3-4822-810D-8EA2A5F5F2B3}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {2A833B3F-CB43-4A31-A37F-8823C70740F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {2A833B3F-CB43-4A31-A37F-8823C70740F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {2A833B3F-CB43-4A31-A37F-8823C70740F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {2A833B3F-CB43-4A31-A37F-8823C70740F1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {9C987DEC-2811-4278-A06E-93D9946F38CE} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using Grpc.Core; 6 | using OpenTracing.Tag; 7 | 8 | namespace OpenTracing.Contrib.Grpc 9 | { 10 | public static class Extensions 11 | { 12 | public static ISpan SetException(this ISpan span, Exception ex) 13 | { 14 | return span?.SetTag(Tags.Error, true) 15 | .Log(new Dictionary(5) 16 | { 17 | {LogFields.Event, Tags.Error.Key}, 18 | {LogFields.ErrorObject, ex}, 19 | 20 | // Those fields will be removed once Configration.WithExpandExceptionLogs is implemented 21 | {LogFields.ErrorKind, ex.GetType().Name}, 22 | {LogFields.Message, ex.Message}, 23 | {LogFields.Stack, ex.StackTrace} 24 | }); 25 | } 26 | 27 | public static TimeSpan TimeRemaining(this DateTime deadline) 28 | { 29 | return deadline.Subtract(DateTime.UtcNow); 30 | } 31 | 32 | public static string ToReadableString(this Metadata metadata) 33 | { 34 | if (metadata.Count == 0) 35 | return null; 36 | 37 | return string.Join(";", metadata.Select(e => $"{e.Key} = {e.Value}")); 38 | } 39 | 40 | public static string ToReadableString(this CallOptions options) 41 | { 42 | // Should this be converted to milis? 43 | var deadline = options.Deadline.HasValue ? options.Deadline.Value.ToUniversalTime().ToString(CultureInfo.InvariantCulture) : "Infinite"; 44 | var headers = options.Headers.ToReadableString() ?? "Empty"; 45 | var writeOptions = options.WriteOptions != null ? options.WriteOptions.Flags.ToString() : "None"; 46 | var isContextPropagated = options.PropagationToken != null; 47 | 48 | return $"Headers: {headers}; " + 49 | $"Deadline: {deadline}; " + 50 | $"IsWaitForReady: {options.IsWaitForReady}; " + 51 | $"WriteOptions: {writeOptions} " + 52 | $"IsContextPropagated: {isContextPropagated}"; 53 | } 54 | 55 | public static string GetAuthorizationHeaderValue(this Metadata headers) 56 | { 57 | var authorization = headers?.FirstOrDefault(x => x.Key.Equals("authorization", StringComparison.OrdinalIgnoreCase))?.Value; 58 | 59 | return string.IsNullOrWhiteSpace(authorization) ? "NoAuth" : authorization; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /OpenTracing.Contrib.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B72DE217-C3BF-4F75-9741-2B74E3847443}" 7 | ProjectSection(SolutionItems) = preProject 8 | src\Directory.Build.props = src\Directory.Build.props 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1C357E7A-E7A2-44DA-896E-C3E3FF24CF25}" 12 | ProjectSection(SolutionItems) = preProject 13 | test\Directory.Build.props = test\Directory.Build.props 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing.Contrib.Grpc", "src\OpenTracing.Contrib.Grpc\OpenTracing.Contrib.Grpc.csproj", "{ADA91FF5-40F4-436E-90EC-1F20138DCCF8}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTracing.Contrib.Grpc.Test", "test\OpenTracing.Contrib.Grpc.Test\OpenTracing.Contrib.Grpc.Test.csproj", "{7BE004FC-793E-4D39-8DAF-D0F9FD6E6205}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A47B38EB-246E-4CD5-9655-539466C71067}" 21 | ProjectSection(SolutionItems) = preProject 22 | Directory.Build.props = Directory.Build.props 23 | README.md = README.md 24 | version.props = version.props 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {ADA91FF5-40F4-436E-90EC-1F20138DCCF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {ADA91FF5-40F4-436E-90EC-1F20138DCCF8}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {ADA91FF5-40F4-436E-90EC-1F20138DCCF8}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {ADA91FF5-40F4-436E-90EC-1F20138DCCF8}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {7BE004FC-793E-4D39-8DAF-D0F9FD6E6205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {7BE004FC-793E-4D39-8DAF-D0F9FD6E6205}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {7BE004FC-793E-4D39-8DAF-D0F9FD6E6205}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {7BE004FC-793E-4D39-8DAF-D0F9FD6E6205}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(NestedProjects) = preSolution 46 | {ADA91FF5-40F4-436E-90EC-1F20138DCCF8} = {B72DE217-C3BF-4F75-9741-2B74E3847443} 47 | {7BE004FC-793E-4D39-8DAF-D0F9FD6E6205} = {1C357E7A-E7A2-44DA-896E-C3E3FF24CF25} 48 | EndGlobalSection 49 | GlobalSection(ExtensibilityGlobals) = postSolution 50 | SolutionGuid = {090F0273-DD20-4E48-91F3-DA8899862364} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/GrpcTraceLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Grpc.Core; 4 | using OpenTracing.Contrib.Grpc.Configuration; 5 | 6 | namespace OpenTracing.Contrib.Grpc 7 | { 8 | internal class GrpcTraceLogger 9 | where TRequest : class 10 | where TResponse : class 11 | { 12 | private readonly ISpan _span; 13 | private readonly TracingConfiguration _configuration; 14 | 15 | private IScope _scope; 16 | private bool _isFinished; 17 | 18 | private ISpan ScopeSpan 19 | { 20 | get 21 | { 22 | lock (this) 23 | { 24 | return _scope?.Span ?? _span; 25 | } 26 | } 27 | } 28 | 29 | public GrpcTraceLogger(ISpan span, TracingConfiguration configuration) 30 | { 31 | _span = span; 32 | _configuration = configuration; 33 | 34 | if (_configuration.Verbose) 35 | { 36 | _span.Log("Started call"); 37 | } 38 | } 39 | 40 | public void ResponseHeader(Metadata metadata) 41 | { 42 | if (!_configuration.Verbose) return; 43 | 44 | _span.Log(new Dictionary 45 | { 46 | { LogFields.Event, "Response headers received" }, 47 | { "data", metadata?.ToReadableString() } 48 | }); 49 | } 50 | 51 | public void BeginInputScope(string operationName) 52 | { 53 | if (!(_configuration.StreamingInputSpans || _configuration.Verbose)) return; 54 | 55 | BeginScope(operationName); 56 | } 57 | 58 | public void BeginOutputScope(string operationName) 59 | { 60 | if (!(_configuration.StreamingOutputSpans || _configuration.Verbose)) return; 61 | 62 | BeginScope(operationName); 63 | } 64 | 65 | private void BeginScope(string operationName) 66 | { 67 | lock (this) 68 | { 69 | if (_scope != null) EndScope(); 70 | 71 | _scope = _configuration.Tracer.BuildSpan(operationName) 72 | .AsChildOf(_span.Context) 73 | .StartActive(false); 74 | } 75 | } 76 | 77 | public void EndInputScope() 78 | { 79 | if (!(_configuration.StreamingInputSpans || _configuration.Verbose)) return; 80 | 81 | EndScope(); 82 | } 83 | 84 | public void EndOutputScope() 85 | { 86 | if (!(_configuration.StreamingOutputSpans || _configuration.Verbose)) return; 87 | 88 | EndScope(); 89 | } 90 | 91 | private void EndScope() 92 | { 93 | lock (this) 94 | { 95 | if (_scope == null) return; 96 | 97 | _scope.Span.Finish(); 98 | _scope.Dispose(); 99 | _scope = null; 100 | } 101 | } 102 | 103 | public void Request(TRequest req) 104 | { 105 | if (!(_configuration.Streaming || _configuration.Verbose)) return; 106 | 107 | ScopeSpan.Log(new Dictionary 108 | { 109 | { LogFields.Event, "gRPC request" }, 110 | { "data", req } 111 | }); 112 | } 113 | 114 | public void Response(TResponse rsp) 115 | { 116 | if (!(_configuration.Streaming || _configuration.Verbose)) return; 117 | 118 | ScopeSpan.Log(new Dictionary 119 | { 120 | { LogFields.Event, "gRPC response" }, 121 | { "data", rsp } 122 | }); 123 | } 124 | 125 | public void FinishSuccess() 126 | { 127 | if (_configuration.Verbose) 128 | { 129 | _span.Log("Call completed"); 130 | } 131 | Finish(); 132 | } 133 | 134 | public void FinishException(Exception ex) 135 | { 136 | if (_configuration.Verbose) 137 | { 138 | _span.Log("Call failed"); 139 | } 140 | _span.SetException(ex); 141 | Finish(); 142 | } 143 | 144 | private void Finish() 145 | { 146 | if (_isFinished) return; 147 | 148 | EndScope(); 149 | _span.Finish(); 150 | _isFinished = true; 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Interceptors/ServerTracingInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using Grpc.Core.Interceptors; 3 | using Grpc.Core.Utils; 4 | using OpenTracing.Contrib.Grpc.Configuration; 5 | using OpenTracing.Contrib.Grpc.Handler; 6 | using System.Collections.Generic; 7 | using System.Threading.Tasks; 8 | using OpenTracing.Contrib.Grpc.OperationNameConstructor; 9 | 10 | namespace OpenTracing.Contrib.Grpc.Interceptors 11 | { 12 | public class ServerTracingInterceptor : Interceptor 13 | { 14 | private readonly ServerTracingConfiguration _configuration; 15 | 16 | public ServerTracingInterceptor(ITracer tracer) 17 | { 18 | GrpcPreconditions.CheckNotNull(tracer, nameof(tracer)); 19 | _configuration = new ServerTracingConfiguration(tracer); 20 | } 21 | 22 | private ServerTracingInterceptor(ServerTracingConfiguration configuration) 23 | { 24 | _configuration = configuration; 25 | } 26 | 27 | public override Task UnaryServerHandler(TRequest request, ServerCallContext context, UnaryServerMethod continuation) 28 | { 29 | return new InterceptedServerHandler(_configuration, context) 30 | .UnaryServerHandler(request, continuation); 31 | } 32 | 33 | public override Task ClientStreamingServerHandler(IAsyncStreamReader requestStream, ServerCallContext context, ClientStreamingServerMethod continuation) 34 | { 35 | return new InterceptedServerHandler(_configuration, context) 36 | .ClientStreamingServerHandler(requestStream, continuation); 37 | } 38 | 39 | public override Task ServerStreamingServerHandler(TRequest request, IServerStreamWriter responseStream, ServerCallContext context, ServerStreamingServerMethod continuation) 40 | { 41 | return new InterceptedServerHandler(_configuration, context) 42 | .ServerStreamingServerHandler(request, responseStream, continuation); 43 | } 44 | 45 | public override Task DuplexStreamingServerHandler(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context, DuplexStreamingServerMethod continuation) 46 | { 47 | return new InterceptedServerHandler(_configuration, context) 48 | .DuplexStreamingServerHandler(requestStream, responseStream, continuation); 49 | } 50 | 51 | public class Builder 52 | { 53 | private readonly ITracer _tracer; 54 | private IOperationNameConstructor _operationNameConstructor; 55 | private bool _streaming; 56 | private bool _streamingInputSpans; 57 | private bool _streamingOutputSpans; 58 | private bool _verbose; 59 | private ISet _tracedAttributes; 60 | 61 | public Builder(ITracer tracer) 62 | { 63 | _tracer = tracer; 64 | } 65 | 66 | /// to name all spans created by this intercepter 67 | /// this Builder with configured operation name 68 | public Builder WithOperationName(IOperationNameConstructor operationNameConstructor) 69 | { 70 | _operationNameConstructor = operationNameConstructor; 71 | return this; 72 | } 73 | 74 | /// 75 | /// Logs streaming events to server spans. 76 | /// 77 | /// this Builder configured to log streaming events 78 | public Builder WithStreaming() 79 | { 80 | _streaming = true; 81 | return this; 82 | } 83 | 84 | /// 85 | /// Creates a child span for each input message received. 86 | /// 87 | /// this Builder configured to create child spans 88 | public Builder WithStreamingInputSpans() 89 | { 90 | _streamingInputSpans = true; 91 | return this; 92 | } 93 | 94 | /// 95 | /// Creates a child span for each output message sent. 96 | /// 97 | /// this Builder configured to create child spans 98 | public Builder WithStreamingOutputSpans() 99 | { 100 | _streamingOutputSpans = true; 101 | return this; 102 | } 103 | 104 | /// 105 | /// Logs all request life-cycle events to server spans. 106 | /// 107 | /// this Builder configured to be verbose 108 | public Builder WithVerbosity() 109 | { 110 | _verbose = true; 111 | return this; 112 | } 113 | 114 | /// to set as tags on server spans created by this intercepter 115 | /// this Builder configured to trace attributes 116 | public Builder WithTracedAttributes(params ServerTracingConfiguration.RequestAttribute[] tracedAttributes) 117 | { 118 | _tracedAttributes = new HashSet(tracedAttributes); 119 | return this; 120 | } 121 | 122 | public ServerTracingInterceptor Build() 123 | { 124 | var configuration = new ServerTracingConfiguration(_tracer, _operationNameConstructor, _streaming, _streamingInputSpans, _streamingOutputSpans, _verbose, _tracedAttributes); 125 | return new ServerTracingInterceptor(configuration); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/visualstudio 3 | 4 | ### VisualStudio ### 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # Visual Studio code coverage results 121 | *.coverage 122 | *.coveragexml 123 | 124 | # NCrunch 125 | _NCrunch_* 126 | .*crunch*.local.xml 127 | nCrunchTemp_* 128 | 129 | # MightyMoose 130 | *.mm.* 131 | AutoTest.Net/ 132 | 133 | # Web workbench (sass) 134 | .sass-cache/ 135 | 136 | # Installshield output folder 137 | [Ee]xpress/ 138 | 139 | # DocProject is a documentation generator add-in 140 | DocProject/buildhelp/ 141 | DocProject/Help/*.HxT 142 | DocProject/Help/*.HxC 143 | DocProject/Help/*.hhc 144 | DocProject/Help/*.hhk 145 | DocProject/Help/*.hhp 146 | DocProject/Help/Html2 147 | DocProject/Help/html 148 | 149 | # Click-Once directory 150 | publish/ 151 | 152 | # Publish Web Output 153 | *.[Pp]ublish.xml 154 | *.azurePubxml 155 | # TODO: Uncomment the next line to ignore your web deploy settings. 156 | # By default, sensitive information, such as encrypted password 157 | # should be stored in the .pubxml.user file. 158 | #*.pubxml 159 | *.pubxml.user 160 | *.publishproj 161 | 162 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 163 | # checkin your Azure Web App publish settings, but sensitive information contained 164 | # in these scripts will be unencrypted 165 | PublishScripts/ 166 | 167 | # NuGet Packages 168 | *.nupkg 169 | # The packages folder can be ignored because of Package Restore 170 | **/packages/* 171 | # except build/, which is used as an MSBuild target. 172 | !**/packages/build/ 173 | # Uncomment if necessary however generally it will be regenerated when needed 174 | #!**/packages/repositories.config 175 | # NuGet v3's project.json files produces more ignorable files 176 | *.nuget.props 177 | *.nuget.targets 178 | 179 | # Microsoft Azure Build Output 180 | csx/ 181 | *.build.csdef 182 | 183 | # Microsoft Azure Emulator 184 | ecf/ 185 | rcf/ 186 | 187 | # Windows Store app package directories and files 188 | AppPackages/ 189 | BundleArtifacts/ 190 | Package.StoreAssociation.xml 191 | _pkginfo.txt 192 | 193 | # Visual Studio cache files 194 | # files ending in .cache can be ignored 195 | *.[Cc]ache 196 | # but keep track of directories ending in .cache 197 | !*.[Cc]ache/ 198 | 199 | # Others 200 | ClientBin/ 201 | ~$* 202 | *~ 203 | *.dbmdl 204 | *.dbproj.schemaview 205 | *.jfm 206 | *.pfx 207 | *.publishsettings 208 | orleans.codegen.cs 209 | 210 | # Since there are multiple workflows, uncomment next line to ignore bower_components 211 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 212 | #bower_components/ 213 | 214 | # RIA/Silverlight projects 215 | Generated_Code/ 216 | 217 | # Backup & report files from converting an old project file 218 | # to a newer Visual Studio version. Backup files are not needed, 219 | # because we have git ;-) 220 | _UpgradeReport_Files/ 221 | Backup*/ 222 | UpgradeLog*.XML 223 | UpgradeLog*.htm 224 | 225 | # SQL Server files 226 | *.mdf 227 | *.ldf 228 | *.ndf 229 | 230 | # Business Intelligence projects 231 | *.rdl.data 232 | *.bim.layout 233 | *.bim_*.settings 234 | 235 | # Microsoft Fakes 236 | FakesAssemblies/ 237 | 238 | # GhostDoc plugin setting file 239 | *.GhostDoc.xml 240 | 241 | # Node.js Tools for Visual Studio 242 | .ntvs_analysis.dat 243 | node_modules/ 244 | 245 | # Typescript v1 declaration files 246 | typings/ 247 | 248 | # Visual Studio 6 build log 249 | *.plg 250 | 251 | # Visual Studio 6 workspace options file 252 | *.opt 253 | 254 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 255 | *.vbw 256 | 257 | # Visual Studio LightSwitch build output 258 | **/*.HTMLClient/GeneratedArtifacts 259 | **/*.DesktopClient/GeneratedArtifacts 260 | **/*.DesktopClient/ModelManifest.xml 261 | **/*.Server/GeneratedArtifacts 262 | **/*.Server/ModelManifest.xml 263 | _Pvt_Extensions 264 | 265 | # Paket dependency manager 266 | .paket/paket.exe 267 | paket-files/ 268 | 269 | # FAKE - F# Make 270 | .fake/ 271 | 272 | # JetBrains Rider 273 | .idea/ 274 | *.sln.iml 275 | 276 | # CodeRush 277 | .cr/ 278 | 279 | # Python Tools for Visual Studio (PTVS) 280 | __pycache__/ 281 | *.pyc 282 | 283 | # Cake - Uncomment if you are using it 284 | # tools/** 285 | # !tools/packages.config 286 | 287 | # Telerik's JustMock configuration file 288 | *.jmconfig 289 | 290 | # BizTalk build output 291 | *.btp.cs 292 | *.btm.cs 293 | *.odx.cs 294 | *.xsd.cs 295 | 296 | ### VisualStudio Patch ### 297 | # By default, sensitive information, such as encrypted password 298 | # should be stored in the .pubxml.user file. 299 | 300 | 301 | # End of https://www.gitignore.io/api/visualstudio 302 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Handler/InterceptedServerHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Grpc.Core; 4 | using OpenTracing.Contrib.Grpc.Configuration; 5 | using OpenTracing.Contrib.Grpc.Propagation; 6 | using OpenTracing.Contrib.Grpc.Streaming; 7 | using OpenTracing.Propagation; 8 | using OpenTracing.Tag; 9 | 10 | namespace OpenTracing.Contrib.Grpc.Handler 11 | { 12 | internal class InterceptedServerHandler 13 | where TRequest : class 14 | where TResponse : class 15 | { 16 | private readonly ServerTracingConfiguration _configuration; 17 | private readonly ServerCallContext _context; 18 | private readonly GrpcTraceLogger _logger; 19 | private readonly StreamActions _inputStreamActions; 20 | private readonly StreamActions _outputStreamActions; 21 | 22 | public InterceptedServerHandler(ServerTracingConfiguration configuration, ServerCallContext context) 23 | { 24 | _configuration = configuration; 25 | _context = context; 26 | 27 | var span = GetSpanFromContext(); 28 | _logger = new GrpcTraceLogger(span, configuration); 29 | 30 | var inputScopeActions = new ScopeActions("new_request", _logger.BeginInputScope, _logger.EndInputScope); 31 | _inputStreamActions = new StreamActions(inputScopeActions, _logger.Request); 32 | 33 | var outputScopeActions = new ScopeActions("new_response", _logger.BeginOutputScope, _logger.EndOutputScope); 34 | _outputStreamActions = new StreamActions(outputScopeActions, _logger.Response); 35 | } 36 | 37 | private ISpan GetSpanFromContext() 38 | { 39 | var spanBuilder = GetSpanBuilderFromHeaders() 40 | .WithTag(Constants.TAGS_PEER_ADDRESS, _context.Peer) 41 | .WithTag(Tags.Component, Constants.TAGS_COMPONENT) 42 | .WithTag(Tags.SpanKind, Tags.SpanKindServer); 43 | 44 | foreach (var attribute in _configuration.TracedAttributes) 45 | { 46 | switch (attribute) 47 | { 48 | case ServerTracingConfiguration.RequestAttribute.MethodName: 49 | spanBuilder.WithTag(Constants.TAGS_GRPC_METHOD_NAME, _context.Method); 50 | break; 51 | case ServerTracingConfiguration.RequestAttribute.Headers: 52 | // TODO: Check if this is always present immediately, especially in case of streaming! 53 | spanBuilder.WithTag(Constants.TAGS_GRPC_HEADERS, _context.RequestHeaders?.ToReadableString()); 54 | break; 55 | } 56 | } 57 | return spanBuilder.StartActive(false).Span; 58 | } 59 | 60 | private ISpanBuilder GetSpanBuilderFromHeaders() 61 | { 62 | var operationName = _configuration.OperationNameConstructor.ConstructOperationName(_context.Method); 63 | var spanBuilder = _configuration.Tracer.BuildSpan(operationName); 64 | 65 | var parentSpanCtx = _configuration.Tracer.Extract(BuiltinFormats.HttpHeaders, new MetadataCarrier(_context.RequestHeaders)); 66 | if (parentSpanCtx != null) 67 | { 68 | spanBuilder = spanBuilder.AsChildOf(parentSpanCtx); 69 | } 70 | return spanBuilder; 71 | } 72 | 73 | public async Task UnaryServerHandler(TRequest request, UnaryServerMethod continuation) 74 | { 75 | try 76 | { 77 | _logger.Request(request); 78 | var response = await continuation(request, _context).ConfigureAwait(false); 79 | _logger.Response(response); 80 | _logger.FinishSuccess(); 81 | return response; 82 | } 83 | catch (Exception ex) 84 | { 85 | _logger.FinishException(ex); 86 | throw; 87 | } 88 | } 89 | 90 | public async Task ClientStreamingServerHandler(IAsyncStreamReader requestStream, ClientStreamingServerMethod continuation) 91 | { 92 | try 93 | { 94 | var tracingRequestStream = new TracingAsyncStreamReader(requestStream, _inputStreamActions); 95 | var response = await continuation(tracingRequestStream, _context).ConfigureAwait(false); 96 | _outputStreamActions.ScopeActions.BeginScope(); 97 | _outputStreamActions.Message(response); 98 | _outputStreamActions.ScopeActions.EndScope(); 99 | _logger.FinishSuccess(); 100 | return response; 101 | } 102 | catch (Exception ex) 103 | { 104 | _logger.FinishException(ex); 105 | throw; 106 | } 107 | } 108 | 109 | public async Task ServerStreamingServerHandler(TRequest request, IServerStreamWriter responseStream, ServerStreamingServerMethod continuation) 110 | { 111 | try 112 | { 113 | var tracingResponseStream = new TracingServerStreamWriter(responseStream, _outputStreamActions); 114 | _inputStreamActions.ScopeActions.BeginScope(); 115 | _inputStreamActions.Message(request); 116 | _inputStreamActions.ScopeActions.EndScope(); 117 | await continuation(request, tracingResponseStream, _context).ConfigureAwait(false); 118 | _logger.FinishSuccess(); 119 | } 120 | catch (Exception ex) 121 | { 122 | _logger.FinishException(ex); 123 | throw; 124 | } 125 | } 126 | 127 | public async Task DuplexStreamingServerHandler(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, DuplexStreamingServerMethod continuation) 128 | { 129 | try 130 | { 131 | var tracingRequestStream = new TracingAsyncStreamReader(requestStream, _inputStreamActions); 132 | var tracingResponseStream = new TracingServerStreamWriter(responseStream, _outputStreamActions); 133 | await continuation(tracingRequestStream, tracingResponseStream, _context).ConfigureAwait(false); 134 | _logger.FinishSuccess(); 135 | } 136 | catch (Exception ex) 137 | { 138 | _logger.FinishException(ex); 139 | throw; 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Interceptors/ClientTracingInterceptor.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using Grpc.Core.Interceptors; 3 | using Grpc.Core.Utils; 4 | using OpenTracing.Contrib.Grpc.Configuration; 5 | using OpenTracing.Contrib.Grpc.Handler; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using OpenTracing.Contrib.Grpc.OperationNameConstructor; 9 | 10 | namespace OpenTracing.Contrib.Grpc.Interceptors 11 | { 12 | public class ClientTracingInterceptor : Interceptor 13 | { 14 | private readonly ClientTracingConfiguration _configuration; 15 | 16 | public ClientTracingInterceptor(ITracer tracer) 17 | { 18 | GrpcPreconditions.CheckNotNull(tracer, nameof(tracer)); 19 | _configuration = new ClientTracingConfiguration(tracer); 20 | } 21 | 22 | private ClientTracingInterceptor(ClientTracingConfiguration configuration) 23 | { 24 | _configuration = configuration; 25 | } 26 | 27 | public override TResponse BlockingUnaryCall(TRequest request, ClientInterceptorContext context, BlockingUnaryCallContinuation continuation) 28 | { 29 | return new InterceptedClientHandler(_configuration, context) 30 | .BlockingUnaryCall(request, continuation); 31 | } 32 | 33 | public override AsyncUnaryCall AsyncUnaryCall(TRequest request, ClientInterceptorContext context, AsyncUnaryCallContinuation continuation) 34 | { 35 | return new InterceptedClientHandler(_configuration, context) 36 | .AsyncUnaryCall(request, continuation); 37 | } 38 | 39 | public override AsyncServerStreamingCall AsyncServerStreamingCall(TRequest request, ClientInterceptorContext context, 40 | AsyncServerStreamingCallContinuation continuation) 41 | { 42 | return new InterceptedClientHandler(_configuration, context) 43 | .AsyncServerStreamingCall(request, continuation); 44 | } 45 | 46 | public override AsyncClientStreamingCall AsyncClientStreamingCall(ClientInterceptorContext context, AsyncClientStreamingCallContinuation continuation) 47 | { 48 | return new InterceptedClientHandler(_configuration, context) 49 | .AsyncClientStreamingCall(continuation); 50 | } 51 | 52 | public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall(ClientInterceptorContext context, AsyncDuplexStreamingCallContinuation continuation) 53 | { 54 | return new InterceptedClientHandler(_configuration, context) 55 | .AsyncDuplexStreamingCall(continuation); 56 | } 57 | 58 | public class Builder 59 | { 60 | private readonly ITracer _tracer; 61 | private IOperationNameConstructor _operationNameConstructor; 62 | private bool _streaming; 63 | private bool _streamingInputSpans; 64 | private bool _streamingOutputSpans; 65 | private bool _verbose; 66 | private ISet _tracedAttributes; 67 | private bool _waitForReady; 68 | private CancellationToken _cancellationToken; 69 | 70 | public Builder(ITracer tracer) 71 | { 72 | _tracer = tracer; 73 | } 74 | 75 | /// to name all spans created by this intercepter 76 | /// this Builder with configured operation name 77 | public Builder WithOperationName(IOperationNameConstructor operationNameConstructor) 78 | { 79 | _operationNameConstructor = operationNameConstructor; 80 | return this; 81 | } 82 | 83 | /// 84 | /// Logs streaming events to client spans. 85 | /// 86 | /// this Builder configured to log streaming events 87 | public Builder WithStreaming() 88 | { 89 | _streaming = true; 90 | return this; 91 | } 92 | 93 | /// 94 | /// Creates a child span for each input message received. 95 | /// 96 | /// this Builder configured to create child spans 97 | public Builder WithStreamingInputSpans() 98 | { 99 | _streamingInputSpans = true; 100 | return this; 101 | } 102 | 103 | /// 104 | /// Creates a child span for each output message sent. 105 | /// 106 | /// this Builder configured to create child spans 107 | public Builder WithStreamingOutputSpans() 108 | { 109 | _streamingOutputSpans = true; 110 | return this; 111 | } 112 | 113 | /// 114 | /// Logs all request life-cycle events to client spans. 115 | /// 116 | /// this Builder configured to be verbose 117 | public Builder WithVerbosity() 118 | { 119 | _verbose = true; 120 | return this; 121 | } 122 | 123 | /// to set as tags on client spans created by this interceptor 124 | /// this Builder configured to trace attributes 125 | public Builder WithTracedAttributes(params ClientTracingConfiguration.RequestAttribute[] tracedAttributes) 126 | { 127 | _tracedAttributes = new HashSet(tracedAttributes); 128 | return this; 129 | } 130 | 131 | /// 132 | /// Enables WaitForReady call option for all calls. 133 | /// 134 | /// this Builder configured to be verbose 135 | public Builder WithWaitForReady() 136 | { 137 | _waitForReady = true; 138 | return this; 139 | } 140 | 141 | /// The cancellation token to set for all RPCs if none was set. 142 | /// this Builder configured to be verbose 143 | public Builder WithFallbackCancellationToken(CancellationToken cancellationToken) 144 | { 145 | _cancellationToken = cancellationToken; 146 | return this; 147 | } 148 | 149 | public ClientTracingInterceptor Build() 150 | { 151 | var configuration = new ClientTracingConfiguration(_tracer, _operationNameConstructor, _streaming, _streamingInputSpans, _streamingOutputSpans, _verbose, _tracedAttributes, _waitForReady, _cancellationToken); 152 | return new ClientTracingInterceptor(configuration); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /getting_started/GreeterShared/HelloworldGrpc.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by the protocol buffer compiler. DO NOT EDIT! 3 | // source: helloworld.proto 4 | // 5 | // Original file comments: 6 | // Copyright 2015 gRPC authors. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | #pragma warning disable 1591 21 | #region Designer generated code 22 | 23 | using grpc = global::Grpc.Core; 24 | 25 | namespace Helloworld { 26 | /// 27 | /// The greeting service definition. 28 | /// 29 | public static partial class Greeter 30 | { 31 | static readonly string __ServiceName = "helloworld.Greeter"; 32 | 33 | static readonly grpc::Marshaller __Marshaller_HelloRequest = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloRequest.Parser.ParseFrom); 34 | static readonly grpc::Marshaller __Marshaller_HelloReply = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::Helloworld.HelloReply.Parser.ParseFrom); 35 | 36 | static readonly grpc::Method __Method_SayHello = new grpc::Method( 37 | grpc::MethodType.Unary, 38 | __ServiceName, 39 | "SayHello", 40 | __Marshaller_HelloRequest, 41 | __Marshaller_HelloReply); 42 | 43 | /// Service descriptor 44 | public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor 45 | { 46 | get { return global::Helloworld.HelloworldReflection.Descriptor.Services[0]; } 47 | } 48 | 49 | /// Base class for server-side implementations of Greeter 50 | public abstract partial class GreeterBase 51 | { 52 | /// 53 | /// Sends a greeting 54 | /// 55 | /// The request received from the client. 56 | /// The context of the server-side call handler being invoked. 57 | /// The response to send back to the client (wrapped by a task). 58 | public virtual global::System.Threading.Tasks.Task SayHello(global::Helloworld.HelloRequest request, grpc::ServerCallContext context) 59 | { 60 | throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, "")); 61 | } 62 | 63 | } 64 | 65 | /// Client for Greeter 66 | public partial class GreeterClient : grpc::ClientBase 67 | { 68 | /// Creates a new client for Greeter 69 | /// The channel to use to make remote calls. 70 | public GreeterClient(grpc::Channel channel) : base(channel) 71 | { 72 | } 73 | /// Creates a new client for Greeter that uses a custom CallInvoker. 74 | /// The callInvoker to use to make remote calls. 75 | public GreeterClient(grpc::CallInvoker callInvoker) : base(callInvoker) 76 | { 77 | } 78 | /// Protected parameterless constructor to allow creation of test doubles. 79 | protected GreeterClient() : base() 80 | { 81 | } 82 | /// Protected constructor to allow creation of configured clients. 83 | /// The client configuration. 84 | protected GreeterClient(ClientBaseConfiguration configuration) : base(configuration) 85 | { 86 | } 87 | 88 | /// 89 | /// Sends a greeting 90 | /// 91 | /// The request to send to the server. 92 | /// The initial metadata to send with the call. This parameter is optional. 93 | /// An optional deadline for the call. The call will be cancelled if deadline is hit. 94 | /// An optional token for canceling the call. 95 | /// The response received from the server. 96 | public virtual global::Helloworld.HelloReply SayHello(global::Helloworld.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 97 | { 98 | return SayHello(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 99 | } 100 | /// 101 | /// Sends a greeting 102 | /// 103 | /// The request to send to the server. 104 | /// The options for the call. 105 | /// The response received from the server. 106 | public virtual global::Helloworld.HelloReply SayHello(global::Helloworld.HelloRequest request, grpc::CallOptions options) 107 | { 108 | return CallInvoker.BlockingUnaryCall(__Method_SayHello, null, options, request); 109 | } 110 | /// 111 | /// Sends a greeting 112 | /// 113 | /// The request to send to the server. 114 | /// The initial metadata to send with the call. This parameter is optional. 115 | /// An optional deadline for the call. The call will be cancelled if deadline is hit. 116 | /// An optional token for canceling the call. 117 | /// The call object. 118 | public virtual grpc::AsyncUnaryCall SayHelloAsync(global::Helloworld.HelloRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) 119 | { 120 | return SayHelloAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken)); 121 | } 122 | /// 123 | /// Sends a greeting 124 | /// 125 | /// The request to send to the server. 126 | /// The options for the call. 127 | /// The call object. 128 | public virtual grpc::AsyncUnaryCall SayHelloAsync(global::Helloworld.HelloRequest request, grpc::CallOptions options) 129 | { 130 | return CallInvoker.AsyncUnaryCall(__Method_SayHello, null, options, request); 131 | } 132 | /// Creates a new instance of client from given ClientBaseConfiguration. 133 | protected override GreeterClient NewInstance(ClientBaseConfiguration configuration) 134 | { 135 | return new GreeterClient(configuration); 136 | } 137 | } 138 | 139 | /// Creates service definition that can be registered with a server 140 | /// An object implementing the server-side handling logic. 141 | public static grpc::ServerServiceDefinition BindService(GreeterBase serviceImpl) 142 | { 143 | return grpc::ServerServiceDefinition.CreateBuilder() 144 | .AddMethod(__Method_SayHello, serviceImpl.SayHello).Build(); 145 | } 146 | 147 | } 148 | } 149 | #endregion 150 | -------------------------------------------------------------------------------- /test/OpenTracing.Contrib.Grpc.Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Grpc.Core; 5 | using Grpc.Core.Interceptors; 6 | using OpenTracing.Contrib.Grpc.Configuration; 7 | using OpenTracing.Contrib.Grpc.Interceptors; 8 | using OpenTracing.Mock; 9 | using Tutorial; 10 | 11 | namespace OpenTracing.Contrib.Grpc.Test 12 | { 13 | internal class Program 14 | { 15 | private class ConsoleMockTracer : MockTracer 16 | { 17 | private volatile object _syncRoot; 18 | 19 | public ConsoleMockTracer(object syncRoot) 20 | { 21 | _syncRoot = syncRoot; 22 | } 23 | 24 | protected override void OnSpanFinished(MockSpan span) 25 | { 26 | lock (_syncRoot) 27 | { 28 | Console.WriteLine(span); 29 | Console.WriteLine("Tags:"); 30 | Console.WriteLine(string.Join("; ", span.Tags.Select(e => $"{e.Key} = {e.Value}"))); 31 | Console.WriteLine("Logs:"); 32 | span.LogEntries.ForEach(entry => 33 | Console.WriteLine($"Timestamp: {entry.Timestamp}, Fields: " 34 | + string.Join("; ", entry.Fields.Select(e => $"{e.Key} = {e.Value}")))); 35 | Console.WriteLine(); 36 | } 37 | } 38 | } 39 | 40 | private static readonly object SyncRoot = new object(); 41 | private static readonly ConsoleMockTracer ServerTracer = new ConsoleMockTracer(SyncRoot); 42 | private static readonly ServerTracingInterceptor ServerTracingInterceptor = new ServerTracingInterceptor 43 | .Builder(ServerTracer) 44 | .WithStreaming() 45 | .WithStreamingInputSpans() 46 | .WithStreamingOutputSpans() 47 | .WithVerbosity() 48 | .WithTracedAttributes(ServerTracingConfiguration.RequestAttribute.Headers, ServerTracingConfiguration.RequestAttribute.MethodName) 49 | .Build(); 50 | 51 | private static Task Main() 52 | { 53 | return MainAsync(); 54 | } 55 | 56 | private static async Task MainAsync() 57 | { 58 | Server server = new Server 59 | { 60 | Ports = { new ServerPort("localhost", 8011, ServerCredentials.Insecure) }, 61 | Services = { Phone.BindService(new PhoneImpl()).Intercept(ServerTracingInterceptor) } 62 | }; 63 | server.Start(); 64 | 65 | var clientTracer = new ConsoleMockTracer(SyncRoot); 66 | var tracingInterceptor = new ClientTracingInterceptor 67 | .Builder(clientTracer) 68 | .WithStreaming() 69 | .WithStreamingInputSpans() 70 | .WithStreamingOutputSpans() 71 | .WithVerbosity() 72 | .WithTracedAttributes(ClientTracingConfiguration.RequestAttribute.AllCallOptions, ClientTracingConfiguration.RequestAttribute.Headers) 73 | .WithWaitForReady() 74 | .Build(); 75 | 76 | Console.WriteLine("Calling unary:"); 77 | var client = new Phone.PhoneClient(new Channel("localhost:8011", ChannelCredentials.Insecure).Intercept(tracingInterceptor)); 78 | var request = new Person { Name = "Karl Heinz" }; 79 | var _ = await client.GetNameAsync(request); 80 | 81 | Console.WriteLine("Calling client stream:"); 82 | var response2 = client.GetNameRequestStream(); 83 | await response2.RequestStream.WriteAsync(request); 84 | await response2.RequestStream.WriteAsync(request); 85 | await response2.RequestStream.WriteAsync(request); 86 | await response2.RequestStream.CompleteAsync(); 87 | await response2.ResponseAsync; 88 | 89 | Console.WriteLine("Calling server stream:"); 90 | var response3 = client.GetNameResponseStream(request); 91 | while (await response3.ResponseStream.MoveNext()) 92 | { 93 | // Ignore 94 | } 95 | 96 | Console.WriteLine("Calling bi-di stream:"); 97 | var response4 = client.GetNameBiDiStream(); 98 | await response4.RequestStream.WriteAsync(request); 99 | await response4.RequestStream.WriteAsync(request); 100 | await response4.RequestStream.WriteAsync(request); 101 | await response4.RequestStream.CompleteAsync(); 102 | while (await response4.ResponseStream.MoveNext()) 103 | { 104 | // Ignore 105 | } 106 | 107 | try 108 | { 109 | var options = 110 | new CallOptions() 111 | .WithHeaders(new Metadata { { "CorrelationId", Guid.NewGuid().ToString() } }) 112 | .WithDeadline(DateTime.UtcNow.AddHours(1)) 113 | .WithWaitForReady(true) 114 | .WithWriteOptions(new WriteOptions(WriteFlags.NoCompress)); 115 | 116 | Console.WriteLine("Calling unary with options:"); 117 | var response5 = await client.GetNameAsync( 118 | new Person { Name = "Test" }, 119 | options); 120 | } 121 | catch 122 | { 123 | // Ignore 124 | } 125 | 126 | await server.ShutdownAsync(); 127 | Console.ReadLine(); 128 | } 129 | 130 | public class PhoneImpl : Phone.PhoneBase 131 | { 132 | public override Task GetName(Person request, ServerCallContext context) 133 | { 134 | if (string.IsNullOrEmpty(request.Name)) 135 | throw new RpcException(new Status(StatusCode.InvalidArgument, "name must not be empty")); 136 | 137 | return Task.FromResult(request); 138 | } 139 | 140 | public override async Task GetNameRequestStream(IAsyncStreamReader requestStream, ServerCallContext context) 141 | { 142 | Person request = null; 143 | while (await requestStream.MoveNext()) 144 | { 145 | request = requestStream.Current; 146 | if (string.IsNullOrEmpty(request.Name)) 147 | throw new RpcException(new Status(StatusCode.InvalidArgument, "name must not be empty")); 148 | } 149 | 150 | return request ?? new Person(); 151 | } 152 | 153 | public override async Task GetNameResponseStream(Person request, IServerStreamWriter responseStream, ServerCallContext context) 154 | { 155 | if (string.IsNullOrEmpty(request.Name)) 156 | throw new RpcException(new Status(StatusCode.InvalidArgument, "name must not be empty")); 157 | 158 | for (int i = 0; i < 3; i++) 159 | { 160 | await responseStream.WriteAsync(request); 161 | await Task.Delay(100); 162 | } 163 | } 164 | 165 | public override async Task GetNameBiDiStream(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) 166 | { 167 | var tracingInterceptor = new ClientTracingInterceptor 168 | .Builder(ServerTracer) 169 | .WithStreaming() 170 | .WithStreamingInputSpans() 171 | .WithStreamingOutputSpans() 172 | .WithVerbosity() 173 | .WithTracedAttributes(ClientTracingConfiguration.RequestAttribute.AllCallOptions, ClientTracingConfiguration.RequestAttribute.Headers) 174 | .WithWaitForReady() 175 | .Build(); 176 | var channel = new Channel("localhost:8011", ChannelCredentials.Insecure); 177 | var client = new Phone.PhoneClient(channel.Intercept(tracingInterceptor)); 178 | 179 | while (await requestStream.MoveNext()) 180 | { 181 | var request = requestStream.Current; 182 | if (string.IsNullOrEmpty(request.Name)) 183 | throw new RpcException(new Status(StatusCode.InvalidArgument, "name must not be empty")); 184 | 185 | var response = await client.GetNameAsync(request, context.RequestHeaders, context.Deadline, context.CancellationToken); 186 | await responseStream.WriteAsync(response); 187 | } 188 | 189 | await channel.ShutdownAsync(); 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status][ci-img]][ci] [![NuGet][nuget-img]][nuget] 2 | 3 | **WARNING: This project is a work in progress and not yet ready for production** 4 | 5 | # OpenTracing gRPC Instrumentation 6 | 7 | OpenTracing instrumentation for gRPC. 8 | 9 | ## Installation 10 | 11 | Install the [NuGet package](https://www.nuget.org/packages/OpenTracing.Contrib.Grpc/): 12 | 13 | Install-Package OpenTracing.Contrib.Grpc 14 | 15 | ## Usage 16 | 17 | ### Server 18 | 19 | - Instantiate tracer 20 | - Create a `ServerTracingInterceptor` 21 | - Intercept a service 22 | 23 | ```csharp 24 | using Grpc.Core; 25 | using Grpc.Core.Interceptors; 26 | using OpenTracing.Contrib.Grpc; 27 | 28 | public class YourServer { 29 | 30 | private readonly string host; 31 | private readonly int port; 32 | private readonly Server server; 33 | private readonly Tracer tracer; 34 | 35 | private void Start() { 36 | ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor(this.tracer); 37 | 38 | Server server = new Server 39 | { 40 | Ports = { new ServerPort(this.host, this.port, ServerCredentials.Insecure) }, 41 | Services = { SomeService.BindService(new SomeServiceImpl()).Intercept(tracingInterceptor) } 42 | }; 43 | server.Start(); 44 | } 45 | } 46 | ``` 47 | 48 | ### Client 49 | 50 | - Instantiate a tracer 51 | - Create a `ClientTracingInterceptor` 52 | - Intercept the client channel 53 | 54 | ```csharp 55 | using Grpc.Core; 56 | using Grpc.Core.Interceptors; 57 | using OpenTracing.Contrib.Grpc; 58 | 59 | public class YourClient { 60 | 61 | private readonly Channel channel; 62 | private readonly Tracer tracer; 63 | private readonly SomeServiceClient client; 64 | 65 | public YourClient(string host, int port) { 66 | this.channel = new Channel(host, port, ChannelCredentials.Insecure); 67 | 68 | ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor(this.tracer); 69 | this.client = new SomeService.SomeServiceClient(this.channel.Intercept(tracingInterceptor)); 70 | } 71 | } 72 | ``` 73 | 74 | ## Server Tracing 75 | 76 | A `ServerTracingInterceptor` uses default settings, which you can override by creating it using a `ServerTracingInterceptor.Builder`. 77 | 78 | - `WithOperationName(IOperationNameConstructor operationName)`: Define how the operation name is constructed for all spans created for this intercepted server. Default is the name of the RPC method. More details in the `Operation Name` section. 79 | - `WithStreaming()`: Logs to the server span whenever a message is received or a response sent. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 80 | - `WithStreamingInputSpans()`: Creates a child span for each incoming message. This is adviced when using long-running streams as the calls' span is only finished when the connection is closed. 81 | - `WithStreamingOutputSpans()`: Creates a child span for each outgoing message. This is adviced when using long-running streams as the calls' span is only finished when the connection is closed. 82 | - `WithVerbosity()`: Logs to the server span additional events, such as message received, headers received and call complete. Default only logs if a call is cancelled. 83 | - `WithTracedAttributes(params ServerRequestAttribute[] attrs)`: Sets tags on the server span in case you want to track information about the RPC call. 84 | 85 | ### Example 86 | 87 | ```csharp 88 | ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor 89 | .Builder(tracer) 90 | .WithStreaming() 91 | .WithStreamingInputSpans() 92 | .WithStreamingOutputSpans() 93 | .WithVerbosity() 94 | .WithOperationName(new PrefixOperationNameConstructor("Server")) 95 | .WithTracedAttributes(ServerTracingConfiguration.RequestAttribute.Headers, 96 | ServerTracingConfiguration.RequestAttribute.MethodType) 97 | .Build(); 98 | ``` 99 | 100 | ## Client Tracing 101 | 102 | A `ClientTracingInterceptor` also has default settings, which you can override by creating it using a `ClientTracingInterceptor.Builder`. 103 | 104 | - `WithOperationName(IOperationNameConstructor operationName)`: Define how the operation name is constructed for all spans created for this intercepted client. Default is the name of the RPC method. More details in the `Operation Name` section. 105 | - `WithStreaming()`: Logs to the client span whenever a message is sent or a response is received. *Note:* This package supports streaming but has not been rigorously tested. If you come across any issues, please let us know. 106 | - `WithStreamingInputSpans()`: Creates a child span for each incoming message. This is adviced when using long-running streams as the calls' span is only finished when the connection is closed. 107 | - `WithStreamingOutputSpans()`: Creates a child span for each outgoing message. This is adviced when using long-running streams as the calls' span is only finished when the connection is closed. 108 | - `WithVerbosity()`: Logs to the client span additional events, such as call started, message sent, headers received, response received, and call complete. Default only logs if a call is cancelled. 109 | - `WithTracedAttributes(params ClientRequestAttribute[] attrs)`: Sets tags on the client span in case you want to track information about the RPC call. 110 | - `WithWaitForReady()`: Enables WaitForReady on all RPC calls. 111 | - `WithFallbackCancellationToken(CancellationToken cancellationToken)`: Sets the cancellation token if the RPC call hasn't defined one. 112 | 113 | ### Example 114 | ```csharp 115 | public class CustomOperationNameConstructor : IOperationNameConstructor 116 | { 117 | public string ConstructOperationName(Method method) 118 | { 119 | // construct some operation name from the method descriptor 120 | } 121 | } 122 | 123 | ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor 124 | .Builder(tracer) 125 | .WithStreaming() 126 | .WithStreamingInputSpans() 127 | .WithStreamingOutputSpans() 128 | .WithVerbosity() 129 | .WithOperationName(new CustomOperationNameConstructor()) 130 | .WithTracingAttributes(ClientTracingConfiguration.RequestAttribute.AllCallOptions, 131 | ClientTracingConfiguration.ClientRequestAttribute.Headers) 132 | .WithWaitForReady() 133 | .WithFallbackCancellationToken(cancellationToken) 134 | .Build(); 135 | ``` 136 | 137 | ## Current Span Context 138 | 139 | In your server request handler, you can access the current active span for that request by calling 140 | 141 | ```csharp 142 | Span span = tracer.ActiveSpan; 143 | ``` 144 | 145 | This is useful if you want to manually set tags on the span, log important events, or create a new child span for internal units of work. You can also use this key to wrap these internal units of work with a new context that has a user-defined active span. 146 | 147 | ## Operation Names 148 | 149 | The default operation name for any span is the RPC method name (`Grpc.Core.Method.FullName`). However, you may want to add your own prefixes, alter the name, or define a new name. For examples of good operation names, check out the OpenTracing `semantics`. 150 | 151 | To alter the operation name, you need to add an implementation of the interface `IOperationNameConstructor` to the `ClientTracingInterceptor.Builder` or `ServerTracingInterceptor.Builder`. For example, if you want to add a prefix to the default operation name of your ClientInterceptor, your code would look like this: 152 | 153 | ```csharp 154 | public class CustomPrefixOperationNameConstructor : IOperationNameConstructor 155 | { 156 | public string ConstructOperationName(Method method) 157 | { 158 | return "your-prefix" + method.FullName; 159 | } 160 | public string ConstructOperationName(string method) 161 | { 162 | return "your-prefix" + method; 163 | } 164 | } 165 | 166 | ClientTracingInterceptor interceptor = ClientTracingInterceptor.Builder ... 167 | .WithOperationName(new CustomPrefixOperationNameConstructor()) 168 | .With.... 169 | .Build() 170 | ``` 171 | 172 | You can also use the default implementation using `PrefixOperationNameConstructor`: 173 | 174 | 175 | ```csharp 176 | ClientTracingInterceptor interceptor = ClientTracingInterceptor.Builder ... 177 | .WithOperationName(new PrefixOperationNameConstructor("your-prefix")) 178 | .With.... 179 | .Build() 180 | ``` 181 | 182 | Due to how the C# version of GRPC interceptors are written, it's currently not possible get more information on the method than the method name in an `ServerTracingInterceptor`. 183 | 184 | ## Integrating with Other Interceptors 185 | GRPC provides `Intercept(Interceptor)` methods that allow you chaining multiple interceptors. Preferably put the tracing interceptor at the top of the interceptor stack so that it traces the entire request lifecycle, including other interceptors: 186 | 187 | ### Server 188 | ```csharp 189 | Server server = new Server 190 | { 191 | Services = { SomeService.BindService(new SomeServiceImpl()).Intercept(someInterceptor).Intercept(someOtherInterceptor).Intercept(serverTracingInterceptor) } 192 | Ports = ... 193 | }; 194 | ``` 195 | 196 | ### Client 197 | ```csharp 198 | client = new SomeService.SomeServiceClient(this.channel.Intercept(someInterceptor).Intercept(someOtherInterceptor).Intercept(clientTracingInterceptor)); 199 | ``` 200 | 201 | [ci-img]: https://ci.appveyor.com/api/projects/status/github/opentracing-contrib/csharp-grpc?svg=true 202 | [ci]: https://ci.appveyor.com/project/opentracing/csharp-grpc 203 | [nuget-img]: https://img.shields.io/nuget/v/OpenTracing.Contrib.Grpc.svg 204 | [nuget]: https://www.nuget.org/packages/OpenTracing.Contrib.Grpc/ 205 | -------------------------------------------------------------------------------- /getting_started/GreeterShared/Helloworld.cs: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: helloworld.proto 3 | #pragma warning disable 1591, 0612, 3021 4 | #region Designer generated code 5 | 6 | using pb = global::Google.Protobuf; 7 | using pbc = global::Google.Protobuf.Collections; 8 | using pbr = global::Google.Protobuf.Reflection; 9 | using scg = global::System.Collections.Generic; 10 | namespace Helloworld { 11 | 12 | /// Holder for reflection information generated from helloworld.proto 13 | public static partial class HelloworldReflection { 14 | 15 | #region Descriptor 16 | /// File descriptor for helloworld.proto 17 | public static pbr::FileDescriptor Descriptor { 18 | get { return descriptor; } 19 | } 20 | private static pbr::FileDescriptor descriptor; 21 | 22 | static HelloworldReflection() { 23 | byte[] descriptorData = global::System.Convert.FromBase64String( 24 | string.Concat( 25 | "ChBoZWxsb3dvcmxkLnByb3RvEgpoZWxsb3dvcmxkIhwKDEhlbGxvUmVxdWVz", 26 | "dBIMCgRuYW1lGAEgASgJIh0KCkhlbGxvUmVwbHkSDwoHbWVzc2FnZRgBIAEo", 27 | "CTJJCgdHcmVldGVyEj4KCFNheUhlbGxvEhguaGVsbG93b3JsZC5IZWxsb1Jl", 28 | "cXVlc3QaFi5oZWxsb3dvcmxkLkhlbGxvUmVwbHkiAEI2Chtpby5ncnBjLmV4", 29 | "YW1wbGVzLmhlbGxvd29ybGRCD0hlbGxvV29ybGRQcm90b1ABogIDSExXYgZw", 30 | "cm90bzM=")); 31 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, 32 | new pbr::FileDescriptor[] { }, 33 | new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { 34 | new pbr::GeneratedClrTypeInfo(typeof(global::Helloworld.HelloRequest), global::Helloworld.HelloRequest.Parser, new[]{ "Name" }, null, null, null), 35 | new pbr::GeneratedClrTypeInfo(typeof(global::Helloworld.HelloReply), global::Helloworld.HelloReply.Parser, new[]{ "Message" }, null, null, null) 36 | })); 37 | } 38 | #endregion 39 | 40 | } 41 | #region Messages 42 | /// 43 | /// The request message containing the user's name. 44 | /// 45 | public sealed partial class HelloRequest : pb::IMessage { 46 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HelloRequest()); 47 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 48 | public static pb::MessageParser Parser { get { return _parser; } } 49 | 50 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 51 | public static pbr::MessageDescriptor Descriptor { 52 | get { return global::Helloworld.HelloworldReflection.Descriptor.MessageTypes[0]; } 53 | } 54 | 55 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 56 | pbr::MessageDescriptor pb::IMessage.Descriptor { 57 | get { return Descriptor; } 58 | } 59 | 60 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 61 | public HelloRequest() { 62 | OnConstruction(); 63 | } 64 | 65 | partial void OnConstruction(); 66 | 67 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 68 | public HelloRequest(HelloRequest other) : this() { 69 | name_ = other.name_; 70 | } 71 | 72 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 73 | public HelloRequest Clone() { 74 | return new HelloRequest(this); 75 | } 76 | 77 | /// Field number for the "name" field. 78 | public const int NameFieldNumber = 1; 79 | private string name_ = ""; 80 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 81 | public string Name { 82 | get { return name_; } 83 | set { 84 | name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); 85 | } 86 | } 87 | 88 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 89 | public override bool Equals(object other) { 90 | return Equals(other as HelloRequest); 91 | } 92 | 93 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 94 | public bool Equals(HelloRequest other) { 95 | if (ReferenceEquals(other, null)) { 96 | return false; 97 | } 98 | if (ReferenceEquals(other, this)) { 99 | return true; 100 | } 101 | if (Name != other.Name) return false; 102 | return true; 103 | } 104 | 105 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 106 | public override int GetHashCode() { 107 | int hash = 1; 108 | if (Name.Length != 0) hash ^= Name.GetHashCode(); 109 | return hash; 110 | } 111 | 112 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 113 | public override string ToString() { 114 | return pb::JsonFormatter.ToDiagnosticString(this); 115 | } 116 | 117 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 118 | public void WriteTo(pb::CodedOutputStream output) { 119 | if (Name.Length != 0) { 120 | output.WriteRawTag(10); 121 | output.WriteString(Name); 122 | } 123 | } 124 | 125 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 126 | public int CalculateSize() { 127 | int size = 0; 128 | if (Name.Length != 0) { 129 | size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); 130 | } 131 | return size; 132 | } 133 | 134 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 135 | public void MergeFrom(HelloRequest other) { 136 | if (other == null) { 137 | return; 138 | } 139 | if (other.Name.Length != 0) { 140 | Name = other.Name; 141 | } 142 | } 143 | 144 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 145 | public void MergeFrom(pb::CodedInputStream input) { 146 | uint tag; 147 | while ((tag = input.ReadTag()) != 0) { 148 | switch(tag) { 149 | default: 150 | input.SkipLastField(); 151 | break; 152 | case 10: { 153 | Name = input.ReadString(); 154 | break; 155 | } 156 | } 157 | } 158 | } 159 | 160 | } 161 | 162 | /// 163 | /// The response message containing the greetings 164 | /// 165 | public sealed partial class HelloReply : pb::IMessage { 166 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new HelloReply()); 167 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 168 | public static pb::MessageParser Parser { get { return _parser; } } 169 | 170 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 171 | public static pbr::MessageDescriptor Descriptor { 172 | get { return global::Helloworld.HelloworldReflection.Descriptor.MessageTypes[1]; } 173 | } 174 | 175 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 176 | pbr::MessageDescriptor pb::IMessage.Descriptor { 177 | get { return Descriptor; } 178 | } 179 | 180 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 181 | public HelloReply() { 182 | OnConstruction(); 183 | } 184 | 185 | partial void OnConstruction(); 186 | 187 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 188 | public HelloReply(HelloReply other) : this() { 189 | message_ = other.message_; 190 | } 191 | 192 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 193 | public HelloReply Clone() { 194 | return new HelloReply(this); 195 | } 196 | 197 | /// Field number for the "message" field. 198 | public const int MessageFieldNumber = 1; 199 | private string message_ = ""; 200 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 201 | public string Message { 202 | get { return message_; } 203 | set { 204 | message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); 205 | } 206 | } 207 | 208 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 209 | public override bool Equals(object other) { 210 | return Equals(other as HelloReply); 211 | } 212 | 213 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 214 | public bool Equals(HelloReply other) { 215 | if (ReferenceEquals(other, null)) { 216 | return false; 217 | } 218 | if (ReferenceEquals(other, this)) { 219 | return true; 220 | } 221 | if (Message != other.Message) return false; 222 | return true; 223 | } 224 | 225 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 226 | public override int GetHashCode() { 227 | int hash = 1; 228 | if (Message.Length != 0) hash ^= Message.GetHashCode(); 229 | return hash; 230 | } 231 | 232 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 233 | public override string ToString() { 234 | return pb::JsonFormatter.ToDiagnosticString(this); 235 | } 236 | 237 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 238 | public void WriteTo(pb::CodedOutputStream output) { 239 | if (Message.Length != 0) { 240 | output.WriteRawTag(10); 241 | output.WriteString(Message); 242 | } 243 | } 244 | 245 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 246 | public int CalculateSize() { 247 | int size = 0; 248 | if (Message.Length != 0) { 249 | size += 1 + pb::CodedOutputStream.ComputeStringSize(Message); 250 | } 251 | return size; 252 | } 253 | 254 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 255 | public void MergeFrom(HelloReply other) { 256 | if (other == null) { 257 | return; 258 | } 259 | if (other.Message.Length != 0) { 260 | Message = other.Message; 261 | } 262 | } 263 | 264 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute] 265 | public void MergeFrom(pb::CodedInputStream input) { 266 | uint tag; 267 | while ((tag = input.ReadTag()) != 0) { 268 | switch(tag) { 269 | default: 270 | input.SkipLastField(); 271 | break; 272 | case 10: { 273 | Message = input.ReadString(); 274 | break; 275 | } 276 | } 277 | } 278 | } 279 | 280 | } 281 | 282 | #endregion 283 | 284 | } 285 | 286 | #endregion Designer generated code 287 | -------------------------------------------------------------------------------- /src/OpenTracing.Contrib.Grpc/Handler/InterceptedClientHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Grpc.Core; 6 | using Grpc.Core.Interceptors; 7 | using OpenTracing.Contrib.Grpc.Configuration; 8 | using OpenTracing.Contrib.Grpc.Propagation; 9 | using OpenTracing.Contrib.Grpc.Streaming; 10 | using OpenTracing.Propagation; 11 | using OpenTracing.Tag; 12 | 13 | namespace OpenTracing.Contrib.Grpc.Handler 14 | { 15 | internal class InterceptedClientHandler 16 | where TRequest : class 17 | where TResponse : class 18 | { 19 | private readonly ClientTracingConfiguration _configuration; 20 | private readonly ClientInterceptorContext _context; 21 | private readonly GrpcTraceLogger _logger; 22 | private readonly StreamActions _inputStreamActions; 23 | private readonly StreamActions _outputStreamActions; 24 | 25 | public InterceptedClientHandler(ClientTracingConfiguration configuration, ClientInterceptorContext context) 26 | { 27 | _configuration = configuration; 28 | _context = context; 29 | 30 | var callOptions = ApplyConfigToCallOptions(_context.Options); 31 | if (!Equals(callOptions, context.Options)) 32 | { 33 | _context = new ClientInterceptorContext(context.Method, context.Host, callOptions); 34 | } 35 | 36 | var span = InitializeSpanWithHeaders(); 37 | _logger = new GrpcTraceLogger(span, configuration); 38 | _configuration.Tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new MetadataCarrier(_context.Options.Headers)); 39 | 40 | var inputScopeActions = new ScopeActions("new_response", _logger.BeginInputScope, _logger.EndInputScope); 41 | _inputStreamActions = new StreamActions(inputScopeActions, _logger.Response, _logger.FinishSuccess, _logger.FinishException); 42 | 43 | var outputScopeActions = new ScopeActions("new_request", _logger.BeginOutputScope, _logger.EndOutputScope); 44 | _outputStreamActions = new StreamActions(outputScopeActions, _logger.Request); 45 | } 46 | 47 | private CallOptions ApplyConfigToCallOptions(CallOptions callOptions) 48 | { 49 | var headers = callOptions.Headers; 50 | if (headers == null || headers.IsReadOnly) 51 | { 52 | // Use empty, writable metadata as base: 53 | var metadata = new Metadata(); 54 | 55 | // Copy elements since it was read-only if it had elements: 56 | foreach (var header in headers ?? Enumerable.Empty()) 57 | { 58 | metadata.Add(header); 59 | } 60 | 61 | callOptions = callOptions.WithHeaders(metadata); 62 | } 63 | 64 | if (_configuration.WaitForReady && callOptions.IsWaitForReady != _configuration.WaitForReady) 65 | { 66 | callOptions = callOptions.WithWaitForReady(); 67 | } 68 | 69 | if (_configuration.FallbackCancellationToken != default && callOptions.CancellationToken != _configuration.FallbackCancellationToken) 70 | { 71 | callOptions = callOptions.WithCancellationToken(_configuration.FallbackCancellationToken); 72 | } 73 | 74 | return callOptions; 75 | } 76 | 77 | private ISpan InitializeSpanWithHeaders() 78 | { 79 | var operationName = _configuration.OperationNameConstructor.ConstructOperationName(_context.Method); 80 | var spanBuilder = _configuration.Tracer.BuildSpan(operationName) 81 | .WithTag(Constants.TAGS_PEER_ADDRESS, _context.Host) 82 | .WithTag(Tags.Component, Constants.TAGS_COMPONENT) 83 | .WithTag(Tags.SpanKind, Tags.SpanKindClient); 84 | 85 | foreach (var attribute in _configuration.TracedAttributes) 86 | { 87 | switch (attribute) 88 | { 89 | case ClientTracingConfiguration.RequestAttribute.MethodType: 90 | spanBuilder.WithTag(Constants.TAGS_GRPC_METHOD_TYPE, _context.Method?.Type.ToString()); 91 | break; 92 | case ClientTracingConfiguration.RequestAttribute.MethodName: 93 | spanBuilder.WithTag(Constants.TAGS_GRPC_METHOD_NAME, _context.Method?.FullName); 94 | break; 95 | case ClientTracingConfiguration.RequestAttribute.Deadline: 96 | spanBuilder.WithTag(Constants.TAGS_GRPC_DEADLINE_MILLIS, _context.Options.Deadline?.TimeRemaining().TotalMilliseconds.ToString(CultureInfo.InvariantCulture)); 97 | break; 98 | case ClientTracingConfiguration.RequestAttribute.Authority: 99 | spanBuilder.WithTag(Constants.TAGS_GRPC_AUTHORITY, _context.Options.Headers.GetAuthorizationHeaderValue()); 100 | break; 101 | case ClientTracingConfiguration.RequestAttribute.AllCallOptions: 102 | spanBuilder.WithTag(Constants.TAGS_GRPC_CALL_OPTIONS, _context.Options.ToReadableString()); 103 | break; 104 | case ClientTracingConfiguration.RequestAttribute.Headers: 105 | // TODO: Check if this is always present immediately, expecially in case of streaming! 106 | spanBuilder.WithTag(Constants.TAGS_GRPC_HEADERS, _context.Options.Headers?.ToReadableString()); 107 | break; 108 | } 109 | } 110 | return spanBuilder.Start(); 111 | } 112 | 113 | private Metadata LogResponseHeader(Task metadata) 114 | { 115 | var responseHeader = metadata.Result; 116 | _logger.ResponseHeader(responseHeader); 117 | return responseHeader; 118 | } 119 | 120 | public TResponse BlockingUnaryCall(TRequest request, Interceptor.BlockingUnaryCallContinuation continuation) 121 | { 122 | try 123 | { 124 | _logger.Request(request); 125 | var response = continuation(request, _context); 126 | _logger.Response(response); 127 | _logger.FinishSuccess(); 128 | return response; 129 | } 130 | catch (Exception ex) 131 | { 132 | _logger.FinishException(ex); 133 | throw; 134 | } 135 | } 136 | 137 | public AsyncUnaryCall AsyncUnaryCall(TRequest request, Interceptor.AsyncUnaryCallContinuation continuation) 138 | { 139 | _logger.Request(request); 140 | var rspCnt = continuation(request, _context); 141 | var rspAsync = rspCnt.ResponseAsync.ContinueWith(rspTask => 142 | { 143 | try 144 | { 145 | var response = rspTask.Result; 146 | _logger.Response(response); 147 | _logger.FinishSuccess(); 148 | return response; 149 | } 150 | catch (AggregateException ex) 151 | { 152 | _logger.FinishException(ex.InnerException); 153 | throw ex.InnerException; 154 | } 155 | }); 156 | var rspHeaderAsync = rspCnt.ResponseHeadersAsync.ContinueWith(LogResponseHeader); 157 | return new AsyncUnaryCall(rspAsync, rspHeaderAsync, rspCnt.GetStatus, rspCnt.GetTrailers, rspCnt.Dispose); 158 | } 159 | 160 | public AsyncServerStreamingCall AsyncServerStreamingCall(TRequest request, Interceptor.AsyncServerStreamingCallContinuation continuation) 161 | { 162 | _logger.Request(request); 163 | var rspCnt = continuation(request, _context); 164 | var tracingResponseStream = new TracingAsyncStreamReader(rspCnt.ResponseStream, _inputStreamActions); 165 | var rspHeaderAsync = rspCnt.ResponseHeadersAsync.ContinueWith(LogResponseHeader); 166 | return new AsyncServerStreamingCall(tracingResponseStream, rspHeaderAsync, rspCnt.GetStatus, rspCnt.GetTrailers, rspCnt.Dispose); 167 | } 168 | 169 | public AsyncClientStreamingCall AsyncClientStreamingCall(Interceptor.AsyncClientStreamingCallContinuation continuation) 170 | { 171 | var rspCnt = continuation(_context); 172 | var tracingRequestStream = new TracingClientStreamWriter(rspCnt.RequestStream, _outputStreamActions); 173 | var rspAsync = rspCnt.ResponseAsync.ContinueWith(rspTask => 174 | { 175 | try 176 | { 177 | var response = rspTask.Result; 178 | _logger.Response(response); 179 | _logger.FinishSuccess(); 180 | return response; 181 | } 182 | catch (AggregateException ex) 183 | { 184 | _logger.FinishException(ex.InnerException); 185 | throw ex.InnerException; 186 | } 187 | }); 188 | var rspHeaderAsync = rspCnt.ResponseHeadersAsync.ContinueWith(LogResponseHeader); 189 | return new AsyncClientStreamingCall(tracingRequestStream, rspAsync, rspHeaderAsync, rspCnt.GetStatus, rspCnt.GetTrailers, rspCnt.Dispose); 190 | } 191 | 192 | public AsyncDuplexStreamingCall AsyncDuplexStreamingCall(Interceptor.AsyncDuplexStreamingCallContinuation continuation) 193 | { 194 | var rspCnt = continuation(_context); 195 | var tracingRequestStream = new TracingClientStreamWriter(rspCnt.RequestStream, _outputStreamActions); 196 | var tracingResponseStream = new TracingAsyncStreamReader(rspCnt.ResponseStream, _inputStreamActions); 197 | var rspHeaderAsync = rspCnt.ResponseHeadersAsync.ContinueWith(LogResponseHeader); 198 | return new AsyncDuplexStreamingCall(tracingRequestStream, tracingResponseStream, rspHeaderAsync, rspCnt.GetStatus, rspCnt.GetTrailers, rspCnt.Dispose); 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------