< Summary

Class:Microsoft.Azure.ServiceBus.Diagnostics.MessageExtensions
Assembly:Microsoft.Azure.ServiceBus
File(s):C:\Git\azure-sdk-for-net\sdk\servicebus\Microsoft.Azure.ServiceBus\src\Extensions\MessageDiagnosticsExtensions.cs
Covered lines:35
Uncovered lines:4
Coverable lines:39
Total lines:153
Line coverage:89.7% (35 of 39)
Covered branches:24
Total branches:26
Branch coverage:92.3% (24 of 26)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
ExtractActivity(...)-90.91%90%
TryExtractId(...)-100%100%
TryExtractContext(...)-85%90%

File(s)

C:\Git\azure-sdk-for-net\sdk\servicebus\Microsoft.Azure.ServiceBus\src\Extensions\MessageDiagnosticsExtensions.cs

#LineLine coverage
 1// Copyright (c) Microsoft. All rights reserved.
 2// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 3
 4namespace Microsoft.Azure.ServiceBus.Diagnostics
 5{
 6    using System;
 7    using System.Collections.Generic;
 8    using System.Diagnostics;
 9
 10    public static class MessageExtensions
 11    {
 12        /// <summary>
 13        /// Creates <see cref="Activity"/> based on the tracing context stored in the <see cref="Message"/>
 14        /// <param name="activityName">Optional Activity name</param>
 15        /// <returns>New <see cref="Activity"/> with tracing context</returns>
 16        /// </summary>
 17        /// <remarks>
 18        /// Tracing context is used to correlate telemetry between producer and consumer and
 19        /// represented by 'Diagnostic-Id' and 'Correlation-Context' properties in <see cref="Message.UserProperties"/>.
 20        ///
 21        /// .NET SDK automatically injects context when sending message to the ServiceBus (if diagnostics is enabled by 
 22        ///
 23        /// <para>
 24        /// 'Diagnostic-Id' uniquely identifies operation that enqueued message
 25        /// </para>
 26        /// <para>
 27        /// 'Correlation-Context' is comma separated list of sting key value pairs represeting optional context for the 
 28        /// </para>
 29        ///
 30        /// If there is no tracing context in the message, this method returns <see cref="Activity"/> without parent.
 31        ///
 32        /// Returned <see cref="Activity"/> needs to be started before it can be used (see example below)
 33        /// </remarks>
 34        /// <example>
 35        /// <code>
 36        /// async Task ProcessAsync()
 37        /// {
 38        ///    var message = await messageReceiver.ReceiveAsync();
 39        ///    var activity = message.ExtractActivity();
 40        ///    activity.Start();
 41        ///    Logger.LogInformation($"Message received, Id = {Activity.Current.Id}")
 42        ///    try
 43        ///    {
 44        ///       // process message
 45        ///    }
 46        ///    catch (Exception ex)
 47        ///    {
 48        ///         Logger.LogError($"Exception {ex}, Id = {Activity.Current.Id}")
 49        ///    }
 50        ///    finally
 51        ///    {
 52        ///         activity.Stop();
 53        ///         // Activity is stopped, we no longer have it in Activity.Current, let's user activity now
 54        ///         Logger.LogInformation($"Message processed, Id = {activity.Id}, Duration = {activity.Duration}")
 55        ///    }
 56        /// }
 57        /// </code>
 58        ///
 59        /// Note that every log is stamped with <see cref="Activity.Current"/>.Id, that could be used within
 60        /// any nested method call (sync or async) - <see cref="Activity.Current"/> is an ambient context that flows wit
 61        ///
 62        /// </example>
 63
 64        public static Activity ExtractActivity(this Message message, string activityName = null)
 65        {
 2066            if (message == null)
 67            {
 068                throw new ArgumentNullException(nameof(message));
 69            }
 70
 2071            if (activityName == null)
 72            {
 1873                activityName = ServiceBusDiagnosticSource.ProcessActivityName;
 74            }
 75
 2076            var activity = new Activity(activityName);
 77
 2078            if (TryExtractId(message, out string id))
 79            {
 1680                activity.SetParentId(id);
 81
 1682                if (message.TryExtractContext(out IList<KeyValuePair<string, string>> ctx))
 83                {
 3284                    foreach (var kvp in ctx)
 85                    {
 886                        activity.AddBaggage(kvp.Key, kvp.Value);
 87                    }
 88                }
 89            }
 90
 2091            return activity;
 92        }
 93
 94        internal static bool TryExtractId(this Message message, out string id)
 95        {
 2096            id = null;
 2097            if (message.UserProperties.TryGetValue(ServiceBusDiagnosticSource.ActivityIdPropertyName,
 2098                out object requestId))
 99            {
 20100                var tmp = requestId as string;
 20101                if (tmp != null && tmp.Trim().Length > 0)
 102                {
 16103                    id = tmp;
 16104                    return true;
 105                }
 106            }
 107
 4108            return false;
 109        }
 110
 111        internal static bool TryExtractContext(this Message message, out IList<KeyValuePair<string, string>> context)
 112        {
 16113            context = null;
 114            try
 115            {
 16116                if (message.UserProperties.TryGetValue(ServiceBusDiagnosticSource.CorrelationContextPropertyName,
 16117                    out object ctxObj))
 118                {
 12119                    string ctxStr = ctxObj as string;
 12120                    if (string.IsNullOrEmpty(ctxStr))
 121                    {
 4122                        return false;
 123                    }
 124
 8125                    var ctxList = ctxStr.Split(',');
 8126                    if (ctxList.Length == 0)
 127                    {
 0128                        return false;
 129                    }
 130
 8131                    context = new List<KeyValuePair<string, string>>();
 48132                    foreach (string item in ctxList)
 133                    {
 16134                        var kvp = item.Split('=');
 16135                        if (kvp.Length == 2)
 136                        {
 8137                            context.Add(new KeyValuePair<string, string>(kvp[0], kvp[1]));
 138                        }
 139                    }
 140
 8141                    return true;
 142                }
 4143            }
 0144            catch (Exception)
 145            {
 146                // ignored, if context is invalid, there nothing we can do:
 147                // invalid context was created by consumer, but if we throw here, it will break message processing on pr
 148                // and producer does not control which context it receives
 0149            }
 4150            return false;
 12151        }
 152    }
 153}