├── 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 |
--------------------------------------------------------------------------------