CurtHagenlocher commented on code in PR #2847: URL: https://github.com/apache/arrow-adbc/pull/2847#discussion_r2157559258
########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; Review Comment: This doesn't appear to be used now. ########## csharp/src/Apache.Arrow.Adbc/Tracing/IActivityTracerExtensions.cs: ########## @@ -0,0 +1,120 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + public static class IActivityTracerExtensions + { + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>Returns a new <see cref="Activity"/> object if there is any listener to the Activity, returns null otherwise</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/>. If an exception is thrown by the delegate, the Activity + /// status is set to <see cref="ActivityStatusCode.Error"/> and an Activity <see cref="ActivityEvent"/> is added to the actitity Review Comment: "activity" is typo'd as "actitity" in a number of places in both this file and in ActivityTrace.cs ########## csharp/src/Apache.Arrow.Adbc/Tracing/TracingConnection.cs: ########## @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; + +namespace Apache.Arrow.Adbc.Tracing +{ + public abstract class TracingConnection : AdbcConnection, IActivityTracer + { + private const string SourceNameDefault = "apache.arrow.adbc"; + private readonly ActivityTrace _trace; + private bool _isDisposed; + + protected TracingConnection(IReadOnlyDictionary<string, string> properties) + { + properties.TryGetValue(AdbcOptions.Telemetry.TraceParent, out string? traceParent); + _trace = new ActivityTrace(this.GetAssemblyName(), this.GetAssemblyVersion(), traceParent); Review Comment: Consider storing the assembly name and version as statics to avoid having to compute them every time a connection is created. ########## csharp/src/Apache.Arrow.Adbc/Tracing/TracingConnection.cs: ########## @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; + +namespace Apache.Arrow.Adbc.Tracing +{ + public abstract class TracingConnection : AdbcConnection, IActivityTracer + { + private const string SourceNameDefault = "apache.arrow.adbc"; + private readonly ActivityTrace _trace; + private bool _isDisposed; Review Comment: `_isDisposed` isn't really doing anything. ########## csharp/src/Apache.Arrow.Adbc/Tracing/SerializableActivity.cs: ########## @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Simplified version of <see cref="Activity"/> that excludes some properties, etc. + /// </summary> + internal class SerializableActivity Review Comment: This is still in the same place. Meanwhile, the FileExporter was moved to a new assembly. Should this move with it? ########## csharp/src/Apache.Arrow.Adbc/Tracing/SemConv.cs: ########## @@ -0,0 +1,76 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +namespace Apache.Arrow.Adbc.Tracing +{ + public static class SemConv Review Comment: `SemConv` feels like impenetrable jargon to me. What about `Config` or `Configuration`? ########## csharp/src/Apache.Arrow.Adbc/Tracing/SemConv.cs: ########## @@ -0,0 +1,76 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +namespace Apache.Arrow.Adbc.Tracing +{ + public static class SemConv + { + public const string Namespace = "db.namespace"; + + public static class Db + { + public static class Client + { + public static class Connection + { + public const string State = "db.client.connection.state"; + public const string SessionId = "db.client.connection.session_id"; + } + } + + public static class Collection + { + public const string Name = "db.collection.name"; + } + + public static class Operation + { + public static string Parameter(string name) + { + return $"db.operation.parameter.{name}"; + } + public const string OperationId = "db.operation.operation_id"; + } + + public static class Query + { + public static string Parameter(string name) + { + return "db.query.parameter" + name; Review Comment: Why is this so different from `Operation.Parameter` both in the lack of a "." and the way the value is produced? ########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; + private bool _isDisposed; + + /// <summary> + /// Constructs a new <see cref="ActivityTrace"/> object. If <paramref name="activitySourceName"/> is set, it provides the + /// activity source name, otherwise the current assembly name is used as the activity source name. + /// </summary> + /// <param name="activitySourceName"></param> + public ActivityTrace(string? activitySourceName = default, string? activitySourceVersion = default, string? traceParent = default) + { + activitySourceName ??= GetType().Assembly.GetName().Name!; + if (string.IsNullOrWhiteSpace(activitySourceName)) + { + throw new ArgumentNullException(nameof(activitySourceName)); + } + + TraceParent = traceParent; + // This is required to be disposed + ActivitySource = new(activitySourceName, activitySourceVersion); + } + + /// <summary> + /// Gets the <see cref="System.Diagnostics.ActivitySource"/>. + /// </summary> + public ActivitySource ActivitySource { get; } + + /// <summary> + /// Gets the name of the <see cref="System.Diagnostics.ActivitySource"/> + /// </summary> + public string ActivitySourceName => ActivitySource.Name; + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>Returns a new <see cref="Activity"/> object if there is any listener to the Activity, returns null otherwise</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/>. If an exception is thrown by the delegate, the Activity + /// status is set to <see cref="ActivityStatusCode.Error"/> and an Activity <see cref="ActivityEvent"/> is added to the actitity + /// and finally the exception is rethrown. + /// </remarks> + public void TraceActivity(Action<Activity?> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); Review Comment: Who's responsible for disposing the `Activity` in these cases where we take a delegate? Shouldn't these be something like ``` try { call(activity); } catch (Exception ex) { TraceException(ex, activity); } finally { activity?.Dispose(); } ``` ? ########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; + private bool _isDisposed; + + /// <summary> + /// Constructs a new <see cref="ActivityTrace"/> object. If <paramref name="activitySourceName"/> is set, it provides the + /// activity source name, otherwise the current assembly name is used as the activity source name. + /// </summary> + /// <param name="activitySourceName"></param> + public ActivityTrace(string? activitySourceName = default, string? activitySourceVersion = default, string? traceParent = default) + { + activitySourceName ??= GetType().Assembly.GetName().Name!; Review Comment: Should the source version similarly be taken from the file version of the assembly if null? ########## csharp/src/Apache.Arrow.Adbc/Tracing/IActivityTracer.cs: ########## @@ -0,0 +1,38 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides a base implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter"/>). + /// </summary> + public interface IActivityTracer : IDisposable Review Comment: Why should this be `IDisposable`? ########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; + private bool _isDisposed; + + /// <summary> + /// Constructs a new <see cref="ActivityTrace"/> object. If <paramref name="activitySourceName"/> is set, it provides the + /// activity source name, otherwise the current assembly name is used as the activity source name. + /// </summary> + /// <param name="activitySourceName"></param> + public ActivityTrace(string? activitySourceName = default, string? activitySourceVersion = default, string? traceParent = default) + { + activitySourceName ??= GetType().Assembly.GetName().Name!; + if (string.IsNullOrWhiteSpace(activitySourceName)) + { + throw new ArgumentNullException(nameof(activitySourceName)); + } + + TraceParent = traceParent; + // This is required to be disposed + ActivitySource = new(activitySourceName, activitySourceVersion); + } + + /// <summary> + /// Gets the <see cref="System.Diagnostics.ActivitySource"/>. + /// </summary> + public ActivitySource ActivitySource { get; } + + /// <summary> + /// Gets the name of the <see cref="System.Diagnostics.ActivitySource"/> + /// </summary> + public string ActivitySourceName => ActivitySource.Name; + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>Returns a new <see cref="Activity"/> object if there is any listener to the Activity, returns null otherwise</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/>. If an exception is thrown by the delegate, the Activity + /// status is set to <see cref="ActivityStatusCode.Error"/> and an Activity <see cref="ActivityEvent"/> is added to the actitity + /// and finally the exception is rethrown. + /// </remarks> + public void TraceActivity(Action<Activity?> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public T TraceActivity<T>(Func<Activity?, T> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task TraceActivityAsync(Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task<T> TraceActivityAsync<T>(Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task TraceActivityAsync(ActivitySource activitySource, Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task<T> TraceActivityAsync<T>(ActivitySource activitySource, Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Gets or sets the trace parent context. + /// </summary> + public string? TraceParent { get; set; } + + /// <summary> + /// Writes the exception to the trace by adding an exception event to the current activity (span). + /// </summary> + /// <param name="exception">The exception to trace.</param> + /// <param name="activity">The current activity where the exception is caught.</param> + /// <param name="escaped"> + /// An indicator that should be set to true if the exception event is recorded + /// at a point where it is known that the exception is escaping the scope of the span/activity. + /// For example, <c>escaped</c> should be <c>true</c> if the exception is caught and re-thrown. + /// However, <c>escaped</c> should be set to <c>false</c> if execution continues in the current scope. + /// </param> + private static void TraceException(Exception exception, Activity? activity) => + WriteTraceException(exception, activity); + + /// <summary> + /// Disposes managed and unmanaged objects. If overridden, ensure to call this base method. + /// </summary> + /// <param name="disposing">An indicator of whether this method is being called from the <c>Dispose</c> method.</param> + private void Dispose(bool disposing) + { + if (!_isDisposed && disposing) + { + ActivitySource.Dispose(); + _isDisposed = true; + } + } + + /// <inheritdoc /> + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private static void WriteTraceException(Exception exception, Activity? activity) + { + activity?.AddException(exception); + activity?.SetStatus(ActivityStatusCode.Error); + } + + private static Activity? StartActivityInternal(string? activityName, ActivitySource activitySource, string? traceParent = default) + { + string fullActivityName = GetActivityName(activityName); + return StartActivity(activitySource, fullActivityName, traceParent); + } + + private static string GetActivityName(string? activityName) + { + string tracingBaseName = string.Empty; + if (!string.IsNullOrWhiteSpace(activityName)) + { + StackTrace stackTrace = new(); + StackFrame? frame = stackTrace.GetFrames().FirstOrDefault(f => f.GetMethod()?.Name == activityName); Review Comment: How expensive is this? Why is it being done only when the activityName is set; is that the intended API for deciding whether or not to capture the stack trace? ########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; + private bool _isDisposed; + + /// <summary> + /// Constructs a new <see cref="ActivityTrace"/> object. If <paramref name="activitySourceName"/> is set, it provides the + /// activity source name, otherwise the current assembly name is used as the activity source name. + /// </summary> + /// <param name="activitySourceName"></param> + public ActivityTrace(string? activitySourceName = default, string? activitySourceVersion = default, string? traceParent = default) + { + activitySourceName ??= GetType().Assembly.GetName().Name!; + if (string.IsNullOrWhiteSpace(activitySourceName)) + { + throw new ArgumentNullException(nameof(activitySourceName)); + } + + TraceParent = traceParent; + // This is required to be disposed + ActivitySource = new(activitySourceName, activitySourceVersion); + } + + /// <summary> + /// Gets the <see cref="System.Diagnostics.ActivitySource"/>. + /// </summary> + public ActivitySource ActivitySource { get; } + + /// <summary> + /// Gets the name of the <see cref="System.Diagnostics.ActivitySource"/> + /// </summary> + public string ActivitySourceName => ActivitySource.Name; + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>Returns a new <see cref="Activity"/> object if there is any listener to the Activity, returns null otherwise</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/>. If an exception is thrown by the delegate, the Activity + /// status is set to <see cref="ActivityStatusCode.Error"/> and an Activity <see cref="ActivityEvent"/> is added to the actitity + /// and finally the exception is rethrown. + /// </remarks> + public void TraceActivity(Action<Activity?> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public T TraceActivity<T>(Func<Activity?, T> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task TraceActivityAsync(Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task<T> TraceActivityAsync<T>(Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task TraceActivityAsync(ActivitySource activitySource, Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task<T> TraceActivityAsync<T>(ActivitySource activitySource, Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Gets or sets the trace parent context. + /// </summary> + public string? TraceParent { get; set; } + + /// <summary> + /// Writes the exception to the trace by adding an exception event to the current activity (span). + /// </summary> + /// <param name="exception">The exception to trace.</param> + /// <param name="activity">The current activity where the exception is caught.</param> + /// <param name="escaped"> + /// An indicator that should be set to true if the exception event is recorded + /// at a point where it is known that the exception is escaping the scope of the span/activity. + /// For example, <c>escaped</c> should be <c>true</c> if the exception is caught and re-thrown. + /// However, <c>escaped</c> should be set to <c>false</c> if execution continues in the current scope. + /// </param> + private static void TraceException(Exception exception, Activity? activity) => + WriteTraceException(exception, activity); + + /// <summary> + /// Disposes managed and unmanaged objects. If overridden, ensure to call this base method. + /// </summary> + /// <param name="disposing">An indicator of whether this method is being called from the <c>Dispose</c> method.</param> + private void Dispose(bool disposing) Review Comment: The traditional `Dispose` pattern isn't necessary given that the class is sealed. ########## csharp/src/Apache.Arrow.Adbc/Tracing/TracingReader.cs: ########## @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Threading; +using System.Threading.Tasks; +using Apache.Arrow.Ipc; + +namespace Apache.Arrow.Adbc.Tracing +{ + public abstract class TracingReader : IArrowArrayStream, IActivityTracer + { + private bool _isDisposed; Review Comment: `_isDisposed` isn't really doing anything. ########## csharp/src/Apache.Arrow.Adbc/Tracing/ActivityTrace.cs: ########## @@ -0,0 +1,325 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Apache.Arrow.Adbc.Tracing +{ + /// <summary> + /// Provides the implementation for a tracing source. If drivers want to enable tracing, + /// they need to add a trace listener (e.g., <see cref="FileExporter.FileExporter"/>). + /// </summary> + public sealed class ActivityTrace + { + internal const string ProductVersionDefault = "1.0.0"; + private bool _isDisposed; + + /// <summary> + /// Constructs a new <see cref="ActivityTrace"/> object. If <paramref name="activitySourceName"/> is set, it provides the + /// activity source name, otherwise the current assembly name is used as the activity source name. + /// </summary> + /// <param name="activitySourceName"></param> + public ActivityTrace(string? activitySourceName = default, string? activitySourceVersion = default, string? traceParent = default) + { + activitySourceName ??= GetType().Assembly.GetName().Name!; + if (string.IsNullOrWhiteSpace(activitySourceName)) + { + throw new ArgumentNullException(nameof(activitySourceName)); + } + + TraceParent = traceParent; + // This is required to be disposed + ActivitySource = new(activitySourceName, activitySourceVersion); + } + + /// <summary> + /// Gets the <see cref="System.Diagnostics.ActivitySource"/>. + /// </summary> + public ActivitySource ActivitySource { get; } + + /// <summary> + /// Gets the name of the <see cref="System.Diagnostics.ActivitySource"/> + /// </summary> + public string ActivitySourceName => ActivitySource.Name; + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>Returns a new <see cref="Activity"/> object if there is any listener to the Activity, returns null otherwise</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/>. If an exception is thrown by the delegate, the Activity + /// status is set to <see cref="ActivityStatusCode.Error"/> and an Activity <see cref="ActivityEvent"/> is added to the actitity + /// and finally the exception is rethrown. + /// </remarks> + public void TraceActivity(Action<Activity?> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public T TraceActivity<T>(Func<Activity?, T> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task TraceActivityAsync(Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public async Task<T> TraceActivityAsync<T>(Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, ActivitySource, traceParent ?? TraceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns></returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task TraceActivityAsync(ActivitySource activitySource, Func<Activity?, Task> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Invokes the delegate within the context of a new started <see cref="Activity"/>. + /// </summary> + /// <typeparam name="T">The return type for the delegate.</typeparam> + /// <param name="activitySource">The <see cref="ActivitySource"/> to start the <see cref="Activity"/> on.</param> + /// <param name="call">The delegate to call within the context of a newly started <see cref="Activity"/></param> + /// <param name="methodName">The name of the method for the activity.</param> + /// <returns>The result of the call to the delegate.</returns> + /// <remarks> + /// Creates and starts a new <see cref="Activity"/> object if there is any listener for the ActivitySource. + /// Passes the Activity to the delegate and invokes the delegate. If there are no exceptions thrown by the delegate the + /// Activity status is set to <see cref="ActivityStatusCode.Ok"/> and the result is returned. + /// If an exception is thrown by the delegate, the Activity status is set to <see cref="ActivityStatusCode.Error"/> + /// and an Event <see cref="ActivityEvent"/> is added to the actitity and finally the exception is rethrown. + /// </remarks> + public static async Task<T> TraceActivityAsync<T>(ActivitySource activitySource, Func<Activity?, Task<T>> call, [CallerMemberName] string? activityName = default, string? traceParent = default) + { + using Activity? activity = StartActivityInternal(activityName, activitySource, traceParent); + try + { + T? result = await call(activity); + if (activity?.Status == ActivityStatusCode.Unset) activity?.SetStatus(ActivityStatusCode.Ok); + return result; + } + catch (Exception ex) + { + TraceException(ex, activity); + throw; + } + } + + /// <summary> + /// Gets or sets the trace parent context. + /// </summary> + public string? TraceParent { get; set; } + + /// <summary> + /// Writes the exception to the trace by adding an exception event to the current activity (span). + /// </summary> + /// <param name="exception">The exception to trace.</param> + /// <param name="activity">The current activity where the exception is caught.</param> + /// <param name="escaped"> + /// An indicator that should be set to true if the exception event is recorded + /// at a point where it is known that the exception is escaping the scope of the span/activity. + /// For example, <c>escaped</c> should be <c>true</c> if the exception is caught and re-thrown. + /// However, <c>escaped</c> should be set to <c>false</c> if execution continues in the current scope. + /// </param> + private static void TraceException(Exception exception, Activity? activity) => + WriteTraceException(exception, activity); + + /// <summary> + /// Disposes managed and unmanaged objects. If overridden, ensure to call this base method. + /// </summary> + /// <param name="disposing">An indicator of whether this method is being called from the <c>Dispose</c> method.</param> + private void Dispose(bool disposing) + { + if (!_isDisposed && disposing) + { + ActivitySource.Dispose(); + _isDisposed = true; + } + } + + /// <inheritdoc /> + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); Review Comment: There's no reason to call `GC.SuppressFinalize` on an instance of a class that doesn't implement a finalizer. Also applies to `TracingReader` and `TracingConnection`. And I think in general, there isn't really a reason to implement the traditional Dispose pattern unless the class is holding an unmanaged resource and has a finalizer. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
