Skip to content

Commit

Permalink
Merge pull request #107 from microsoft/release/update/210308110047
Browse files Browse the repository at this point in the history
Queued up bug fixes from pre Rename.  See notes for updates.
  • Loading branch information
MattB-msft authored Mar 8, 2021
2 parents 7df20ec + 439cd9b commit 1fe9bf8
Show file tree
Hide file tree
Showing 14 changed files with 422 additions and 91 deletions.
31 changes: 27 additions & 4 deletions src/GeneralTools/DataverseClient/Client/ConnectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,9 @@ internal sealed class ConnectionService : IConnectionService, IDisposable
private string _ServiceCACHEName = "Microsoft.PowerPlatform.Dataverse.Client.Service"; // this is the base cache key name that will be used to cache the service.

//OAuth Params
private string _clientId; // client id to register your application for OAuth
private Uri _redirectUri; // uri specifying the redirection uri post OAuth auth
private PromptBehavior _promptBehavior; // prompt behavior
private string _tokenCachePath; // user specified token cache file path
private string _resource; // Resource to connect to
private bool _isOnPremOAuth = false; // Identifies whether the connection is for OnPrem or Online Deployment for OAuth
private static string _authority; //cached authority reading from credential manager
private static string _userId = null; //cached userid reading from config file
private bool _isCalledbyExecuteRequest = false; //Flag indicating that the an request called by Execute_Command
private bool _isDefaultCredsLoginForOAuth = false; //Flag indicating that the user is trying to login with the current user id.
Expand All @@ -119,6 +115,27 @@ internal sealed class ConnectionService : IConnectionService, IDisposable
/// if Set to true then the connection is for one use and should be cleand out of cache when completed.
/// </summary>
private bool unqueInstance = false;

/// <summary>
/// Client or App Id to use.
/// </summary>
private string _clientId;

/// <summary>
/// uri specifying the redirection uri post OAuth auth
/// </summary>
private Uri _redirectUri;

/// <summary>
/// Resource to connect to
/// </summary>
private string _resource;

/// <summary>
/// cached authority reading from credential manager
/// </summary>
internal static string _authority;

/// <summary>
/// when certificate Auth is used, this is the certificate that is used to execute the connection.
/// </summary>
Expand Down Expand Up @@ -1370,6 +1387,12 @@ internal void SetClonedProperties(ServiceClient sourceClient)
TenantId = sourceClient.TenantId;
EnvironmentId = sourceClient.EnvironmentId;
GetAccessTokenAsync = sourceClient.GetAccessToken;
_clientId = sourceClient._connectionSvc._clientId;
_certificateStoreLocation = sourceClient._connectionSvc._certificateStoreLocation;
_certificateThumbprint = sourceClient._connectionSvc._certificateThumbprint;
_certificateOfConnection = sourceClient._connectionSvc._certificateOfConnection;
_redirectUri = sourceClient._connectionSvc._redirectUri;
_resource = sourceClient._connectionSvc._resource;
}

#region WebAPI Interface Utilities
Expand Down
125 changes: 94 additions & 31 deletions src/GeneralTools/DataverseClient/Client/DataverseTraceLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
using Microsoft.Xrm.Sdk;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using Microsoft.Rest;
using Newtonsoft.Json.Linq;
using Microsoft.PowerPlatform.Dataverse.Client.Utils;
using Microsoft.Extensions.Logging;

namespace Microsoft.PowerPlatform.Dataverse.Client
{
Expand All @@ -20,15 +22,17 @@ namespace Microsoft.PowerPlatform.Dataverse.Client
internal sealed class DataverseTraceLogger : TraceLoggerBase
{
// Internal connection of exceptions since last clear.
private List<Exception> _ActiveExceptionsList;
private List<Exception> _ActiveExceptionsList;

private ILogger _logger;

#region Properties
/// <summary>
/// Last Error from CRM
/// </summary>
public new string LastError
{
get { return base.LastError.ToString(); }
get { return base.LastError; }
}

/// <summary>
Expand Down Expand Up @@ -80,6 +84,15 @@ public DataverseTraceLogger(string traceSourceName = "")
base.Initialize();
}

public DataverseTraceLogger(ILogger logger)
{
_logger = logger;
TraceSourceName = DefaultTraceSourceName;
_ActiveExceptionsList = new List<Exception>();
base.Initialize();
}


public override void ResetLastError()
{
if (base.LastError.Length > 0 )
Expand All @@ -105,7 +118,7 @@ public void ClearLogCache()
/// <param name="message"></param>
public override void Log(string message)
{
Source.TraceEvent(TraceEventType.Information, (int)TraceEventType.Information, message);
TraceEvent(TraceEventType.Information, (int)TraceEventType.Information, message, null);
}

/// <summary>
Expand All @@ -115,11 +128,14 @@ public override void Log(string message)
/// <param name="eventType"></param>
public override void Log(string message, TraceEventType eventType)
{
TraceEvent(eventType, (int)eventType, message);
if (eventType == TraceEventType.Error)
{
Log(message, eventType, new Exception(message));
}
else
{
TraceEvent(eventType, (int)eventType, message, null);
}
}

/// <summary>
Expand All @@ -144,10 +160,10 @@ public override void Log(string message, TraceEventType eventType, Exception exc
if (!(exception != null && _ActiveExceptionsList.Contains(exception))) // Skip this line if its already been done.
GetExceptionDetail(exception, detailedDump, 0, lastMessage);

TraceEvent(eventType, (int)eventType, detailedDump.ToString());
TraceEvent(eventType, (int)eventType, detailedDump.ToString(), exception);
if (eventType == TraceEventType.Error)
{
base.LastError.Append(lastMessage.ToString());
base.LastError += lastMessage.ToString();
if (!(exception != null && _ActiveExceptionsList.Contains(exception))) // Skip this line if its already been done.
{
// check and or alter the exception is its and HTTPOperationExecption.
Expand Down Expand Up @@ -175,13 +191,13 @@ public override void Log(string message, TraceEventType eventType, Exception exc
public override void Log(Exception exception)
{
if (exception != null && _ActiveExceptionsList.Contains(exception))
return; // allready logged this one .
return; // already logged this one .

StringBuilder detailedDump = new StringBuilder();
StringBuilder lastMessage = new StringBuilder();
GetExceptionDetail(exception, detailedDump, 0, lastMessage);
TraceEvent(TraceEventType.Error, (int)TraceEventType.Error, detailedDump.ToString());
base.LastError.Append(lastMessage.ToString());
TraceEvent(TraceEventType.Error, (int)TraceEventType.Error, detailedDump.ToString(), exception);
base.LastError += lastMessage.ToString();
LastException = exception;

_ActiveExceptionsList.Add(exception);
Expand All @@ -196,10 +212,17 @@ public override void Log(Exception exception)
/// <param name="eventType"></param>
/// <param name="id"></param>
/// <param name="message"></param>
private void TraceEvent(TraceEventType eventType, int id, string message)
/// <param name="ex"></param>
private void TraceEvent(TraceEventType eventType, int id, string message , Exception ex)
{
Source.TraceEvent(eventType, id, message);

LogLevel logLevel = TranslateTraceEventType(eventType);
if (_logger != null && _logger.IsEnabled(logLevel))
{
_logger.Log(logLevel, id, ex, message);
}

if (EnabledInMemoryLogCapture)
{
Logs.Enqueue(Tuple.Create<DateTime, string>(DateTime.UtcNow,
Expand Down Expand Up @@ -253,7 +276,7 @@ private void GetExceptionDetail(object objException, StringBuilder sw, int level
FormatExceptionMessage(
OrgFault.Source != null ? OrgFault.Source.ToString().Trim() : "Not Provided",
OrgFault.TargetSite != null ? OrgFault.TargetSite.Name.ToString() : "Not Provided",
OrgFault.Detail != null ? string.Format(CultureInfo.InvariantCulture, "Message: {0}\nErrorCode: {1}\nTrace: {2}{3}", OrgFault.Detail.Message, OrgFault.Detail.ErrorCode, OrgFault.Detail.TraceText, string.IsNullOrEmpty(ErrorDetail) ? "" : $"\n{ErrorDetail}") :
OrgFault.Detail != null ? string.Format(CultureInfo.InvariantCulture, "Message: {0}\nErrorCode: {1}{4}\nTrace: {2}{3}", OrgFault.Detail.Message, OrgFault.Detail.ErrorCode, OrgFault.Detail.TraceText, string.IsNullOrEmpty(ErrorDetail) ? "" : $"\n{ErrorDetail}" , OrgFault.Detail.ActivityId == null ? "" : $"\nActivityId: {OrgFault.Detail.ActivityId}") :
string.IsNullOrEmpty(OrgFault.Message) ? "Not Provided" : OrgFault.Message.ToString().Trim(),
string.IsNullOrEmpty(OrgFault.HelpLink) ? "Not Provided" : OrgFault.HelpLink.ToString().Trim(),
string.IsNullOrEmpty(OrgFault.StackTrace) ? "Not Provided" : OrgFault.StackTrace.ToString().Trim()
Expand All @@ -280,7 +303,7 @@ private void GetExceptionDetail(object objException, StringBuilder sw, int level
OrganizationServiceFault oFault = (OrganizationServiceFault)objException;
string ErrorDetail = GenerateOrgErrorDetailsInfo(oFault.ErrorDetails);
FormatOrgFaultMessage(
string.Format(CultureInfo.InvariantCulture, "Message: {0}\nErrorCode: {1}\nTrace: {2}{3}", oFault.Message, oFault.ErrorCode, oFault.TraceText, string.IsNullOrEmpty(ErrorDetail) ? "" : $"\n{ErrorDetail}"),
string.Format(CultureInfo.InvariantCulture, "Message: {0}\nErrorCode: {1}{4}\nTrace: {2}{3}", oFault.Message, oFault.ErrorCode, oFault.TraceText, string.IsNullOrEmpty(ErrorDetail) ? "" : $"\n{ErrorDetail}", oFault.ActivityId == null ? "" : $"\nActivityId: {oFault.ActivityId}"),
oFault.Timestamp.ToString(),
oFault.ErrorCode.ToString(),
string.IsNullOrEmpty(oFault.HelpLink) ? "Not Provided" : oFault.HelpLink.ToString().Trim(),
Expand All @@ -302,39 +325,41 @@ private void GetExceptionDetail(object objException, StringBuilder sw, int level
{
if (objException is HttpOperationException httpOperationException)
{
JObject contentBody = JObject.Parse(httpOperationException.Response.Content);
JObject contentBody = null;
if (!string.IsNullOrEmpty(httpOperationException.Response.Content))
contentBody = JObject.Parse(httpOperationException.Response.Content);

var ErrorBlock = contentBody["error"];
var ErrorBlock = contentBody?["error"];
FormatExceptionMessage(
httpOperationException.Source != null ? httpOperationException.Source.ToString().Trim() : "Not Provided",
httpOperationException.TargetSite != null ? httpOperationException.TargetSite.Name?.ToString() : "Not Provided",
string.IsNullOrEmpty(ErrorBlock["message"]?.ToString()) ? "Not Provided" : GetFirstLineFromString(ErrorBlock["message"]?.ToString()).Trim(),
string.IsNullOrEmpty(ErrorBlock?["message"]?.ToString()) ? "Not Provided" : string.Format("Message: {0}{1}\n", GetFirstLineFromString(ErrorBlock?["message"]?.ToString()).Trim(), httpOperationException.Response != null && httpOperationException.Response.Headers.ContainsKey("REQ_ID") ? $"\nActivityId: {ExtractString(httpOperationException.Response.Headers["REQ_ID"])}" : "") ,
string.IsNullOrEmpty(httpOperationException.HelpLink) ? "Not Provided" : httpOperationException.HelpLink.ToString().Trim(),
string.IsNullOrEmpty(ErrorBlock["stacktrace"]?.ToString()) ? "Not Provided" : ErrorBlock["stacktrace"]?.ToString().Trim()
string.IsNullOrEmpty(ErrorBlock?["stacktrace"]?.ToString()) ? "Not Provided" : ErrorBlock["stacktrace"]?.ToString().Trim()
, sw, level);

lastErrorMsg.Append(string.IsNullOrEmpty(httpOperationException.Message) ? "Not Provided" : httpOperationException.Message.ToString().Trim());

// WebEx currently only returns 1 leve of error.
var InnerError = contentBody["error"]["innererror"];
// WebEx currently only returns 1 level of error.
var InnerError = contentBody?["error"]["innererror"];
if (lastErrorMsg.Length > 0 && InnerError != null)
{
level++;
lastErrorMsg.Append(" => ");
FormatExceptionMessage(
httpOperationException.Source != null ? httpOperationException.Source.ToString().Trim() : "Not Provided",
httpOperationException.TargetSite != null ? httpOperationException.TargetSite.Name?.ToString() : "Not Provided",
string.IsNullOrEmpty(InnerError["message"]?.ToString()) ? "Not Provided" : GetFirstLineFromString(InnerError["message"]?.ToString()).Trim(),
string.IsNullOrEmpty(InnerError["@Microsoft.PowerApps.CDS.HelpLink"]?.ToString()) ? "Not Provided" : GetFirstLineFromString(InnerError["@Microsoft.PowerApps.CDS.HelpLink"]?.ToString()).Trim(),
string.IsNullOrEmpty(InnerError["stacktrace"]?.ToString()) ? "Not Provided" : InnerError["stacktrace"]?.ToString().Trim()
string.IsNullOrEmpty(InnerError?["message"]?.ToString()) ? "Not Provided" : GetFirstLineFromString(InnerError?["message"]?.ToString()).Trim(),
string.IsNullOrEmpty(InnerError?["@Microsoft.PowerApps.CDS.HelpLink"]?.ToString()) ? "Not Provided" : GetFirstLineFromString(InnerError?["@Microsoft.PowerApps.CDS.HelpLink"]?.ToString()).Trim(),
string.IsNullOrEmpty(InnerError?["stacktrace"]?.ToString()) ? "Not Provided" : InnerError?["stacktrace"]?.ToString().Trim()
, sw, level);
}
}
else
{
if (objException is DataverseOperationException cdsOpExecp)
{
FormatCdsSvcFaultMessage(
FormatSvcFaultMessage(
string.IsNullOrEmpty(cdsOpExecp.Message) ? "Not Provided" : cdsOpExecp.Message.ToString().Trim(),
string.IsNullOrEmpty(cdsOpExecp.Source) ? "Not Provided" : cdsOpExecp.Source.ToString().Trim(),
cdsOpExecp.HResult == -1 ? "Not Provided" : cdsOpExecp.HResult.ToString().Trim(),
Expand Down Expand Up @@ -382,12 +407,30 @@ private void GetExceptionDetail(object objException, StringBuilder sw, int level
return;
}

/// <summary>
/// returns the first line from the text block.
/// </summary>
/// <param name="textBlock"></param>
/// <returns></returns>
internal static string GetFirstLineFromString(string textBlock)
private static string ExtractString(IEnumerable<string> enumerable)
{
string sOut = string.Empty;
if (enumerable != null)
{
List<string> lst = new List<string>(enumerable);

foreach (var itm in lst.Distinct())
{
if (string.IsNullOrEmpty(sOut))
sOut += $"{itm}";
else
sOut += $"|{itm}";
}
}
return sOut;
}

/// <summary>
/// returns the first line from the text block.
/// </summary>
/// <param name="textBlock"></param>
/// <returns></returns>
internal static string GetFirstLineFromString(string textBlock)
{
if (!string.IsNullOrEmpty(textBlock))
{
Expand Down Expand Up @@ -477,11 +520,11 @@ private static void FormatOrgFaultMessage(string message, string timeOfEvent, st
/// <param name="helpLink">Help Link</param>
/// <param name="sw">Writer to write too</param>
/// <param name="level">Depth</param>
private static void FormatCdsSvcFaultMessage(string message, string source, string errorCode, System.Collections.IDictionary dataItems , string helpLink, StringBuilder sw, int level)
private static void FormatSvcFaultMessage(string message, string source, string errorCode, System.Collections.IDictionary dataItems , string helpLink, StringBuilder sw, int level)
{
if (level != 0)
sw.AppendLine($"Inner Exception Level {level}\t: ");
sw.AppendLine("==CdsClientOperationException Info=======================================================================================");
sw.AppendLine("==DataverseOperationException Info=======================================================================================");
sw.AppendLine($"Source: {source}");
sw.AppendLine("Error: " + message);
sw.AppendLine("ErrorCode: " + errorCode);
Expand All @@ -490,14 +533,34 @@ private static void FormatCdsSvcFaultMessage(string message, string source, stri
sw.AppendLine($"HelpLink Url: {helpLink}");
if (dataItems != null && dataItems.Count > 0)
{
sw.AppendLine("CdsErrorDetail:");
sw.AppendLine("DataverseErrorDetail:");
foreach (System.Collections.DictionaryEntry itm in dataItems)
{
sw.AppendLine($"\t{itm.Key}: {itm.Value}");
}
}
sw.AppendLine("======================================================================================================================");
}

private static LogLevel TranslateTraceEventType(TraceEventType traceLevel)
{
switch (traceLevel)
{
case TraceEventType.Critical:
return LogLevel.Critical;
case TraceEventType.Error:
return LogLevel.Error;
case TraceEventType.Warning:
return LogLevel.Warning;
case TraceEventType.Information:
return LogLevel.Information;
case TraceEventType.Verbose:
return LogLevel.Trace;
default:
return LogLevel.None;
}
}

}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public interface IOrganizationServiceAsync
/// <param name="entityName">Logical name of entity</param>
/// <param name="id">Id of entity</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
void DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken);
Task DeleteAsync(string entityName, Guid id, CancellationToken cancellationToken);

/// <summary>
/// Perform an action in an organization specified by the request.
Expand All @@ -69,7 +69,7 @@ public interface IOrganizationServiceAsync
/// <param name="relationship"></param>
/// <param name="relatedEntities"></param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
void AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken);
Task AssociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken);

/// <summary>
/// Disassociate an entity with a set of entities
Expand All @@ -79,7 +79,7 @@ public interface IOrganizationServiceAsync
/// <param name="relationship"></param>
/// <param name="relatedEntities"></param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
void DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken);
Task DisassociateAsync(string entityName, Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities, CancellationToken cancellationToken);

/// <summary>
/// Retrieves a collection of entities
Expand Down
Loading

0 comments on commit 1fe9bf8

Please sign in to comment.