• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-dotnet / 388636

03 May 2024 02:02PM UTC coverage: 78.171% (+0.02%) from 78.153%
388636

push

CI-PR build

web-flow
Microsoft.Identity.Client bump (#6779)

* Microsoft.Identity.Client bump

* Compensated for new ManagedIdentityClient auto-retrying requests

---------

Co-authored-by: Tracy Boehrer <trboehre@microsoft.com>

26189 of 33502 relevant lines covered (78.17%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.0
/libraries/Microsoft.Bot.Builder/TranscriptLoggerMiddleware.cs
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
// Licensed under the MIT License.
3

4
using System;
5
using System.Collections.Generic;
6
using System.Threading;
7
using System.Threading.Tasks;
8
using Microsoft.Bot.Schema;
9
using Newtonsoft.Json;
10

11
namespace Microsoft.Bot.Builder
12
{
13
    /// <summary>
14
    /// Middleware for logging incoming and outgoing activities to an <see cref="ITranscriptStore"/>.
15
    /// </summary>
16
    public class TranscriptLoggerMiddleware : IMiddleware
17
    {
18
        private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, MaxDepth = null };
1✔
19
        private readonly ITranscriptLogger _logger;
20

21
        /// <summary>
22
        /// Initializes a new instance of the <see cref="TranscriptLoggerMiddleware"/> class.
23
        /// </summary>
24
        /// <param name="transcriptLogger">The conversation store to use.</param>
25
        public TranscriptLoggerMiddleware(ITranscriptLogger transcriptLogger)
1✔
26
        {
27
            _logger = transcriptLogger ?? throw new ArgumentNullException(nameof(transcriptLogger), "TranscriptLoggerMiddleware requires a ITranscriptLogger implementation.  ");
×
28
        }
1✔
29

30
        /// <summary>
31
        /// Records incoming and outgoing activities to the conversation store.
32
        /// </summary>
33
        /// <param name="turnContext">The context object for this turn.</param>
34
        /// <param name="nextTurn">The delegate to call to continue the bot middleware pipeline.</param>
35
        /// <param name="cancellationToken">A cancellation token that can be used by other objects
36
        /// or threads to receive notice of cancellation.</param>
37
        /// <returns>A task that represents the work queued to execute.</returns>
38
        /// <seealso cref="ITurnContext"/>
39
        /// <seealso cref="Bot.Schema.IActivity"/>
40
        public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
41
        {
42
            var transcript = new Queue<IActivity>();
1✔
43

44
            // log incoming activity at beginning of turn
45
            if (turnContext.Activity != null)
1✔
46
            {
47
                turnContext.Activity.From ??= new ChannelAccount();
×
48

49
                if (string.IsNullOrEmpty((string)turnContext.Activity.From.Properties["role"]) && string.IsNullOrEmpty(turnContext.Activity.From.Role))
1✔
50
                {
51
                    turnContext.Activity.From.Role = RoleTypes.User;
1✔
52
                }
53

54
                // We should not log ContinueConversation events used by skills to initialize the middleware.
55
                if (!(turnContext.Activity.Type == ActivityTypes.Event && turnContext.Activity.Name == ActivityEventNames.ContinueConversation))
1✔
56
                {
57
                    LogActivity(transcript, CloneActivity(turnContext.Activity));
1✔
58
                }
59
            }
60

61
            // hook up onSend pipeline
62
            turnContext.OnSendActivities(async (ctx, activities, nextSend) =>
1✔
63
            {
1✔
64
                // run full pipeline
1✔
65
                var responses = await nextSend().ConfigureAwait(false);
1✔
66

1✔
67
                foreach (var activity in activities)
1✔
68
                {
1✔
69
                    LogActivity(transcript, CloneActivity(activity));
1✔
70
                }
1✔
71

1✔
72
                return responses;
1✔
73
            });
1✔
74

75
            // hook up update activity pipeline
76
            turnContext.OnUpdateActivity(async (ctx, activity, nextUpdate) =>
1✔
77
            {
1✔
78
                // run full pipeline
1✔
79
                var response = await nextUpdate().ConfigureAwait(false);
1✔
80

1✔
81
                // add Message Update activity
1✔
82
                var updateActivity = CloneActivity(activity);
1✔
83
                updateActivity.Type = ActivityTypes.MessageUpdate;
1✔
84
                LogActivity(transcript, updateActivity);
1✔
85
                return response;
1✔
86
            });
1✔
87

88
            // hook up delete activity pipeline
89
            turnContext.OnDeleteActivity(async (ctx, reference, nextDelete) =>
1✔
90
            {
1✔
91
                // run full pipeline
1✔
92
                await nextDelete().ConfigureAwait(false);
1✔
93

1✔
94
                // add MessageDelete activity
1✔
95
                // log as MessageDelete activity
1✔
96
                var deleteActivity = new Activity
1✔
97
                    {
1✔
98
                        Type = ActivityTypes.MessageDelete,
1✔
99
                        Id = reference.ActivityId,
1✔
100
                    }
1✔
101
                    .ApplyConversationReference(reference, isIncoming: false)
1✔
102
                    .AsMessageDeleteActivity();
1✔
103

1✔
104
                LogActivity(transcript, deleteActivity);
1✔
105
            });
1✔
106

107
            // process bot logic
108
            await nextTurn(cancellationToken).ConfigureAwait(false);
1✔
109

110
            // flush transcript at end of turn
111
            // NOTE: We are not awaiting this task by design, TryLogTranscriptAsync() observes all exceptions and we don't need to or want to block execution on the completion.
112
            _ = TryLogTranscriptAsync(_logger, transcript);
1✔
113
        }
1✔
114

115
        /// <summary>
116
        /// Helper to sequentially flush the transcript queue to the log.
117
        /// </summary>
118
        private static async Task TryLogTranscriptAsync(ITranscriptLogger logger, Queue<IActivity> transcript)
119
        {
120
            try
121
            {
122
                while (transcript.Count > 0)
1✔
123
                {
124
                    // Process the queue and log all the activities in parallel.
125
                    var activity = transcript.Dequeue();
1✔
126
                    await logger.LogActivityAsync(activity).ConfigureAwait(false);
1✔
127
                }
128
            }
1✔
129
#pragma warning disable CA1031 // Do not catch general exception types (this should probably be addressed later, but for now we just log the error and continue the execution)
130
            catch (Exception ex)
×
131
#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler
132
            {
133
                System.Diagnostics.Trace.TraceError($"Transcript logActivity failed with {ex}");
×
134
            }
×
135
        }
1✔
136

137
        private static IActivity CloneActivity(IActivity activity)
138
        {
139
            activity = JsonConvert.DeserializeObject<Activity>(JsonConvert.SerializeObject(activity, _jsonSettings),  new JsonSerializerSettings { MaxDepth = null });
1✔
140
            var activityWithId = EnsureActivityHasId(activity);
1✔
141

142
            return activityWithId;
1✔
143
        }
144

145
        private static IActivity EnsureActivityHasId(IActivity activity)
146
        {
147
            var activityWithId = activity;
1✔
148

149
            if (activity == null)
1✔
150
            {
151
                throw new ArgumentNullException(nameof(activity), "Cannot check or add Id on a null Activity.");
×
152
            }
153

154
            if (string.IsNullOrEmpty(activity.Id))
1✔
155
            {
156
                var generatedId = $"g_{Guid.NewGuid()}";
1✔
157
                activity.Id = generatedId;
1✔
158
            }
159

160
            return activityWithId;
1✔
161
        }
162

163
        private static void LogActivity(Queue<IActivity> transcript, IActivity activity)
164
        {
165
            activity.Timestamp ??= DateTime.UtcNow;
1✔
166
            transcript.Enqueue(activity);
1✔
167
        }
1✔
168
    }
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc