| | 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.Diagnostics; |
| | 7 | | using System.Diagnostics.Tracing; |
| | 8 | | using System.Linq; |
| | 9 | | using System.Text; |
| | 10 | | using Azure.Core.Diagnostics; |
| | 11 | | using NUnit.Framework; |
| | 12 | | using NUnit.Framework.Interfaces; |
| | 13 | |
|
| | 14 | | namespace Azure.Core.TestFramework |
| | 15 | | { |
| | 16 | | /// <summary> |
| | 17 | | /// The TestLogger listens for the AzureSDK logging event source and traces |
| | 18 | | /// the output so it's easy to view the logs when testing. |
| | 19 | | /// |
| | 20 | | /// We create an instance in SearchTestBase when we want to log test |
| | 21 | | /// output. |
| | 22 | | /// </summary> |
| | 23 | | internal class TestLogger : IDisposable |
| | 24 | | { |
| | 25 | | /// <summary> |
| | 26 | | /// EventSource listener for AzureSDK events. |
| | 27 | | /// </summary> |
| 0 | 28 | | private AzureEventSourceListener Listener { get; } |
| | 29 | |
|
| | 30 | | /// <summary> |
| | 31 | | /// Start collecting AzureSDK events to log. |
| | 32 | | /// </summary> |
| 0 | 33 | | public TestLogger() |
| | 34 | | { |
| 0 | 35 | | Listener = new AzureEventSourceListener( |
| 0 | 36 | | (e, _) => LogEvent(e), |
| 0 | 37 | | EventLevel.Verbose); |
| 0 | 38 | | } |
| | 39 | |
|
| | 40 | | /// <summary> |
| | 41 | | /// Trace any SDK events. |
| | 42 | | /// </summary> |
| | 43 | | /// <param name="args">Event arguments.</param> |
| | 44 | | public void LogEvent(EventWrittenEventArgs args) |
| | 45 | | { |
| 0 | 46 | | var category = args.EventName; |
| 0 | 47 | | IDictionary<string, string> payload = GetPayload(args); |
| | 48 | |
|
| | 49 | | // If there's a request ID, use it after the category |
| 0 | 50 | | var message = new StringBuilder(); |
| 0 | 51 | | if (payload.TryGetValue("requestId", out var requestId)) |
| | 52 | | { |
| 0 | 53 | | payload.Remove("requestId"); |
| 0 | 54 | | message.Append(requestId); |
| | 55 | | } |
| 0 | 56 | | message.AppendLine(); |
| | 57 | |
|
| | 58 | | // Add the rest of the payload |
| 0 | 59 | | foreach (KeyValuePair<string, string> arg in payload) |
| | 60 | | { |
| 0 | 61 | | message.AppendFormat(" {0}: ", arg.Key); |
| | 62 | |
|
| | 63 | | // Don't indent the content's lines |
| 0 | 64 | | if (arg.Key == "content" && arg.Value.Length > 0) |
| | 65 | | { |
| 0 | 66 | | message.AppendLine("\n" + arg.Value); |
| 0 | 67 | | continue; |
| | 68 | | } |
| | 69 | |
|
| 0 | 70 | | var lines = arg.Value.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); |
| 0 | 71 | | if (lines.Length == 1) |
| | 72 | | { |
| | 73 | | // If there's only one line, write it after the name |
| 0 | 74 | | message.AppendLine(lines[0]); |
| | 75 | | } |
| | 76 | | else |
| | 77 | | { |
| | 78 | | // Otherwise add a newline and indent each nested line |
| 0 | 79 | | message.AppendLine(); |
| 0 | 80 | | foreach (var line in lines.Select(l => $" {l}")) |
| | 81 | | { |
| 0 | 82 | | message.AppendLine(line); |
| | 83 | | } |
| | 84 | | } |
| | 85 | | } |
| | 86 | |
|
| | 87 | | // Dump the message and category |
| 0 | 88 | | Trace.WriteLine(message, category); |
| | 89 | |
|
| | 90 | | // Add a line to separate requests/responses within a single test |
| 0 | 91 | | message.AppendLine(); |
| | 92 | | // Output the request/response |
| 0 | 93 | | TestContext.Out.WriteLine(message.ToString()); |
| 0 | 94 | | } |
| | 95 | |
|
| | 96 | | /// <summary> |
| | 97 | | /// Convert the event arguments into a dictionary of strings. |
| | 98 | | /// </summary> |
| | 99 | | /// <param name="args">The event arguments.</param> |
| | 100 | | /// <returns>A dictionary of strings.</returns> |
| | 101 | | private static IDictionary<string, string> GetPayload(EventWrittenEventArgs args) |
| | 102 | | { |
| 0 | 103 | | var payload = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); |
| 0 | 104 | | for (var i = 0; i < args.Payload.Count; i++) |
| | 105 | | { |
| 0 | 106 | | var name = args.PayloadNames[i]; |
| 0 | 107 | | var value = ""; |
| 0 | 108 | | switch (args.Payload[i]) |
| | 109 | | { |
| | 110 | | case null: |
| | 111 | | break; |
| | 112 | | case string s: |
| 0 | 113 | | value = s; |
| 0 | 114 | | break; |
| | 115 | | case byte[] content: |
| 0 | 116 | | value = Encoding.UTF8.GetString(content); |
| | 117 | | // Control characters mess up copy/pasting so we'll |
| | 118 | | // swap them with the SUB character |
| 0 | 119 | | value = new string(value.Select(ch => !char.IsControl(ch) ? ch : '�').ToArray()); |
| 0 | 120 | | break; |
| | 121 | | default: |
| 0 | 122 | | value = args.Payload[i].ToString(); |
| | 123 | | break; |
| | 124 | | } |
| 0 | 125 | | payload.Add(name, value); |
| | 126 | | } |
| 0 | 127 | | return payload; |
| | 128 | | } |
| | 129 | |
|
| | 130 | | /// <summary> |
| | 131 | | /// Cleans up the <see cref="AzureEventSourceListener"/> instance. |
| | 132 | | /// </summary> |
| 0 | 133 | | public void Dispose() => Listener?.Dispose(); |
| | 134 | | } |
| | 135 | | } |