Skip to content

Commit 0e2cbb2

Browse files
Add exception information into telemetry (#239)
* Show correct client request id on debug message * Support generated cmdlet * Update code * update code * update code * Update code after review * initial check in for Azure PowerShell related exceptions (#237) * initial check in for Azure PowerShell related exceptions * code refine * add Azure PowerShell exceptions * Add new telemetry properties * Update after review * Update code * Add method name Co-authored-by: erich-wang <[email protected]>
1 parent 154a957 commit 0e2cbb2

17 files changed

+1403
-15
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System.Collections.Generic;
16+
17+
namespace Microsoft.Azure.Commands.Common
18+
{
19+
/// <summary>
20+
/// Represent key names in Exception.Data for Azure PowerShell.
21+
/// </summary>
22+
public static class AzurePSErrorDataKeys
23+
{
24+
/// <summary>
25+
/// Key prefix, user could add any other keys into Exception.Data with this prefix
26+
/// </summary>
27+
public static readonly string KeyPrefix = "AzPS";
28+
29+
/// <summary>
30+
/// Key for ErrorKind
31+
/// </summary>
32+
public static readonly string ErrorKindKey = KeyPrefix + "ErrorKind";
33+
34+
/// <summary>
35+
/// Key for desensitized error message
36+
/// </summary>
37+
public static readonly string DesensitizedErrorMessageKey = KeyPrefix + "DesensitizedMessage";
38+
39+
/// <summary>
40+
/// Key for status code from http response
41+
/// </summary>
42+
public static readonly string HttpStatusCode = KeyPrefix + "HttpStatusCode";
43+
44+
/// <summary>
45+
/// Key for authentication error code which normally comes from MSAL.NET
46+
/// </summary>
47+
public static readonly string AuthErrorCodeKey = KeyPrefix + "AuthErrorCode";
48+
49+
/// <summary>
50+
/// Key for parameter name which usually used in Argument related exception
51+
/// </summary>
52+
public static readonly string ParamNameKey = KeyPrefix + "ParamName";
53+
54+
/// <summary>
55+
/// Key for file name which usually used in IO related exception
56+
/// </summary>
57+
public static readonly string FileNameKey = KeyPrefix + "FileName";
58+
59+
/// <summary>
60+
/// Key for dictionary key which usually used in AzPSKeyNotFoundException
61+
/// </summary>
62+
public static readonly string NotFoundKeyNameKey = KeyPrefix + "NotFoundKeyName";
63+
64+
/// <summary>
65+
/// Key for line number where exception is thrown
66+
/// </summary>
67+
public static readonly string ErrorLineNumberKey = KeyPrefix + "ErrorLineNumber";
68+
69+
/// <summary>
70+
/// Key for file name in which exception is thrown
71+
/// </summary>
72+
public static readonly string ErrorFileNameKey = KeyPrefix + "ErrorFileName";
73+
74+
/// <summary>
75+
/// Key for hresult returned from underlying OS API
76+
/// </summary>
77+
public static readonly string ErrorHResultKey = KeyPrefix + "HResult";
78+
79+
/// <summary>
80+
/// Determine whether the "key" is predefined one
81+
/// </summary>
82+
/// <param name="key"></param>
83+
/// <returns></returns>
84+
public static bool IsKeyPredefined(string key)
85+
{
86+
return KeyList.Contains(key);
87+
}
88+
89+
/// <summary>
90+
/// Known key name list
91+
/// </summary>
92+
private static HashSet<string> KeyList = new HashSet<string>(new string[] {
93+
ErrorKindKey,
94+
DesensitizedErrorMessageKey,
95+
HttpStatusCode,
96+
AuthErrorCodeKey,
97+
ParamNameKey,
98+
FileNameKey,
99+
NotFoundKeyNameKey,
100+
ErrorLineNumberKey,
101+
ErrorFileNameKey,
102+
ErrorHResultKey,
103+
});
104+
}
105+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.Common
16+
{
17+
/// <summary>
18+
/// Represent Error Data
19+
/// </summary>
20+
public interface IContainsAzPSErrorData
21+
{
22+
/// <summary>
23+
/// Error Kind: User, Serivce, Internal
24+
/// </summary>
25+
ErrorKind ErrorKind { get; }
26+
27+
/// <summary>
28+
/// Desensitized error message
29+
/// </summary>
30+
string DesensitizedErrorMessage { get; }
31+
32+
/// <summary>
33+
/// Line number where exception is thrown
34+
/// </summary>
35+
int? ErrorLineNumber { get; }
36+
37+
/// <summary>
38+
/// File name in which exception is thrown
39+
/// </summary>
40+
string ErrorFileName { get; }
41+
42+
}
43+
44+
/// <summary>
45+
/// Represent Error Kind
46+
/// </summary>
47+
public class ErrorKind
48+
{
49+
public string Value { get; private set; }
50+
51+
private ErrorKind(string value)
52+
{
53+
Value = value;
54+
}
55+
56+
public static implicit operator string(ErrorKind error) => error.Value;
57+
58+
/// <summary>
59+
/// Error caused by user input
60+
/// </summary>
61+
public static ErrorKind UserError = new ErrorKind("User");
62+
63+
/// <summary>
64+
/// Error caused by Azure services
65+
/// </summary>
66+
public static ErrorKind ServiceError = new ErrorKind("Service");
67+
68+
/// <summary>
69+
/// Error that belongs to neither UserError nor ServiceError
70+
/// </summary>
71+
public static ErrorKind InternalError = new ErrorKind("Internal");
72+
}
73+
}

src/Common/AzurePSCmdlet.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ protected AzurePSDataCollectionProfile _dataCollectionProfile
8787
protected static string _sessionId = Guid.NewGuid().ToString();
8888
protected const string _fileTimeStampSuffixFormat = "yyyy-MM-dd-THH-mm-ss-fff";
8989
protected string _clientRequestId = Guid.NewGuid().ToString();
90+
protected static DateTimeOffset? _previousEndTime = null;
9091
protected MetricHelper _metricHelper;
9192
protected AzurePSQoSEvent _qosEvent;
9293
protected DebugStreamTraceListener _adalListener;
@@ -376,15 +377,18 @@ protected override void BeginProcessing()
376377
SessionState = base.SessionState;
377378
var profile = _dataCollectionProfile;
378379
//TODO: Inject from CI server
379-
lock (lockObject)
380+
if(_metricHelper == null)
380381
{
381-
if (_metricHelper == null)
382+
lock (lockObject)
382383
{
383-
_metricHelper = new MetricHelper(profile);
384-
_metricHelper.AddTelemetryClient(new TelemetryClient
384+
if (_metricHelper == null)
385385
{
386-
InstrumentationKey = "7df6ff70-8353-4672-80d6-568517fed090"
387-
});
386+
_metricHelper = new MetricHelper(profile);
387+
_metricHelper.AddTelemetryClient(new TelemetryClient
388+
{
389+
InstrumentationKey = "7df6ff70-8353-4672-80d6-568517fed090"
390+
});
391+
}
388392
}
389393
}
390394

@@ -410,6 +414,7 @@ protected override void EndProcessing()
410414
LogCmdletEndInvocationInfo();
411415
TearDownDebuggingTraces();
412416
TearDownHttpClientPipeline();
417+
_previousEndTime = DateTimeOffset.Now;
413418
base.EndProcessing();
414419
}
415420

@@ -597,7 +602,8 @@ protected virtual void InitializeQosEvent()
597602
ClientRequestId = this._clientRequestId,
598603
SessionId = _sessionId,
599604
IsSuccess = true,
600-
ParameterSetName = this.ParameterSetName
605+
ParameterSetName = this.ParameterSetName,
606+
PreviousEndTime = _previousEndTime
601607
};
602608

603609
if (AzVersion == null)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System;
16+
using System.IO;
17+
using System.Runtime.CompilerServices;
18+
19+
namespace Microsoft.Azure.Commands.Common.Exceptions
20+
{
21+
public class AzPSApplicationException : ApplicationException, IContainsAzPSErrorData
22+
{
23+
public ErrorKind ErrorKind
24+
{
25+
get => Data.GetValue<ErrorKind>(AzurePSErrorDataKeys.ErrorKindKey);
26+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorKindKey, value);
27+
}
28+
29+
public string DesensitizedErrorMessage
30+
{
31+
get => Data.GetValue<string>(AzurePSErrorDataKeys.DesensitizedErrorMessageKey);
32+
private set => Data.SetValue(AzurePSErrorDataKeys.DesensitizedErrorMessageKey, value);
33+
}
34+
35+
public int? ErrorLineNumber
36+
{
37+
get => Data.GetNullableValue<int>(AzurePSErrorDataKeys.ErrorLineNumberKey);
38+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorLineNumberKey, value);
39+
}
40+
41+
public string ErrorFileName
42+
{
43+
get => Data.GetValue<string>(AzurePSErrorDataKeys.ErrorFileNameKey);
44+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorFileNameKey, value);
45+
}
46+
47+
public AzPSApplicationException(
48+
string message,
49+
Exception innerException = null,
50+
string desensitizedMessage = null,
51+
[CallerLineNumber] int lineNumber = 0,
52+
[CallerFilePath] string filePath = null)
53+
: this(message, ErrorKind.InternalError, innerException, desensitizedMessage, lineNumber, filePath)
54+
{
55+
}
56+
57+
public AzPSApplicationException(
58+
string message,
59+
ErrorKind errorKind,
60+
Exception innerException = null,
61+
string desensitizedMessage = null,
62+
[CallerLineNumber] int lineNumber = 0,
63+
[CallerFilePath] string filePath = null)
64+
: base(message, innerException)
65+
{
66+
ErrorKind = errorKind;
67+
DesensitizedErrorMessage = desensitizedMessage;
68+
ErrorLineNumber = lineNumber;
69+
if (!string.IsNullOrEmpty(filePath))
70+
{
71+
ErrorFileName = Path.GetFileNameWithoutExtension(filePath);
72+
}
73+
}
74+
}
75+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System;
16+
using System.IO;
17+
using System.Runtime.CompilerServices;
18+
19+
namespace Microsoft.Azure.Commands.Common.Exceptions
20+
{
21+
public class AzPSArgumentException : ArgumentException, IContainsAzPSErrorData
22+
{
23+
private string ErrorParamName
24+
{
25+
get => Data.GetValue<string>(AzurePSErrorDataKeys.ParamNameKey);
26+
set => Data.SetValue(AzurePSErrorDataKeys.ParamNameKey, value);
27+
}
28+
29+
public ErrorKind ErrorKind
30+
{
31+
get => Data.GetValue<ErrorKind>(AzurePSErrorDataKeys.ErrorKindKey);
32+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorKindKey, value);
33+
}
34+
35+
public string DesensitizedErrorMessage
36+
{
37+
get => Data.GetValue<string>(AzurePSErrorDataKeys.DesensitizedErrorMessageKey);
38+
private set => Data.SetValue(AzurePSErrorDataKeys.DesensitizedErrorMessageKey, value);
39+
}
40+
41+
public int? ErrorLineNumber
42+
{
43+
get => Data.GetNullableValue<int>(AzurePSErrorDataKeys.ErrorLineNumberKey);
44+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorLineNumberKey, value);
45+
}
46+
47+
public string ErrorFileName
48+
{
49+
get => Data.GetValue<string>(AzurePSErrorDataKeys.ErrorFileNameKey);
50+
private set => Data.SetValue(AzurePSErrorDataKeys.ErrorFileNameKey, value);
51+
}
52+
53+
public AzPSArgumentException(
54+
string message,
55+
string paramName,
56+
Exception innerException = null,
57+
string desensitizedMessage = null,
58+
[CallerLineNumber] int lineNumber = 0,
59+
[CallerFilePath] string filePath = null)
60+
: this(message, paramName, ErrorKind.UserError, innerException, desensitizedMessage, lineNumber, filePath)
61+
{
62+
}
63+
64+
public AzPSArgumentException(
65+
string message,
66+
string paramName,
67+
ErrorKind errorKind,
68+
Exception innerException = null,
69+
string desensitizedMessage = null,
70+
[CallerLineNumber] int lineNumber = 0,
71+
[CallerFilePath] string filePath = null)
72+
:base(message, paramName, innerException)
73+
{
74+
ErrorParamName = paramName;
75+
ErrorKind = errorKind;
76+
DesensitizedErrorMessage = desensitizedMessage;
77+
ErrorLineNumber = lineNumber;
78+
79+
if (!string.IsNullOrEmpty(filePath))
80+
{
81+
ErrorFileName = Path.GetFileNameWithoutExtension(filePath);
82+
}
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)