How to get Logic App Metrics? - azure

I'm trying to get Logic Apps metrics like BillableExecutions, Latency etc in my console application.
Currently I'm able to list the logic apps runs, triggers, versions using the .Net Client Microsoft.Azure.Management. But it doesn't seem to have the API to access the monitoring API's.
Code excerpt
private static void Main(string[] args)
{
var token = GetTokenCredentials();
var client = new LogicManagementClient(token, new HttpClientHandler())
{
SubscriptionId = new AzureSubscription().SubscriptionId
};
var dataQuery = new ODataQuery<WorkflowFilter>
{
Top = 50
};
using (client)
{
var logicAppsWorkFlows = client.Workflows.ListBySubscription(dataQuery);
foreach (var logicAppsWorkFlow in logicAppsWorkFlows)
{
var runs = GetWorkflowRuns(client, logicAppsWorkFlow.Id.Split('/')[4], logicAppsWorkFlow.Name);
Console.WriteLine(runs.Count);
}
Console.WriteLine(logicAppsWorkFlows.Count());
}
}
Can someone tell me how to access Logic Apps Metrics? Is there a client similar to Microsoft.Azure.Management for access metrics data?
Update 2
I have found a client dll which was in pre release mode which is used to get metrics. Below is my current code
var token = GetTokenCredentials();
var insightsClient = new InsightsClient(token, new HttpClientHandler())
{
SubscriptionId = new AzureSubscription().SubscriptionId
};
var logicManagementClient = new LogicManagementClient(token, new HttpClientHandler())
{
SubscriptionId = new AzureSubscription().SubscriptionId
};
var dataQuery = new ODataQuery<WorkflowFilter>
{
Top = 50
};
using (logicManagementClient)
{
var logicAppsWorkFlows = logicManagementClient.Workflows.ListBySubscription(dataQuery);
foreach (var logicAppsWorkFlow in logicAppsWorkFlows)
{
using (insightsClient)
{
var metricsDataQuery = new ODataQuery<Metric>
{
Filter = "name.value eq 'ActionLatency' and startTime ge '2014-07-16'"
};
IEnumerable<Metric> metricsList = null;
try
{
metricsList = insightsClient.Metrics.List(logicAppsWorkFlow.Id, metricsDataQuery);
}
catch (Exception e)
{
Console.WriteLine(e);
}
if (metricsList == null) continue;
foreach (var metric in metricsList)
{
foreach (var metricValue in metric.Data)
{
Console.WriteLine(metric.Name.Value + " = " + metricValue.Total);
}
}
}
}
}
I'm getting an exception saying the filter string is not valid. Im referring the filter string structure provided here
https://learn.microsoft.com/en-us/rest/api/monitor/filter-syntax
Can someone tell what im doing wrong here?
Thanks

It looks like ge is not allowed for Logic Apps StartTime field for some reason. I had to change the code to below to make it work
using (logicManagementClient)
{
var logicAppsWorkFlows = logicManagementClient.Workflows.ListBySubscription(dataQuery);
foreach (var logicAppsWorkFlow in logicAppsWorkFlows)
{
using (insightsClient)
{
var metricsDataQuery = new ODataQuery<Metric>
{
Filter = "startTime eq " + DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + " and name.value eq 'BillableTriggerExecutions' and endTime eq " + DateTime.Now.ToString("yyyy-MM-dd")
};
var query = metricsDataQuery.GetQueryString();
Console.WriteLine(query);
IEnumerable<Metric> metricsList = null;
try
{
//throws exception if there is no metrics data
//TODO: Check whether the logic app ran atleast one time
metricsList = insightsClient.Metrics.List(logicAppsWorkFlow.Id, metricsDataQuery);
}
catch (Exception e)
{
Console.WriteLine(e);
}
if (metricsList == null) continue;
foreach (var metric in metricsList)
{
foreach (var metricValue in metric.Data)
{
Console.WriteLine(metric.Name.Value + " = " + metricValue.Total);
}
}
}
}
}

Related

Access Microsoft.Storage/storageAccounts/blobServices metrics from code

I want to retrieve metrics related to Blob metric namespace from Azure Storage Account. I need to read the BlobCount value.
Initially I have tried like this:
var usedCapacityResults = await metricsClient.QueryResourceAsync(resourceId, new[] { "BlobCount1" },
new MetricsQueryOptions
{
MetricNamespace = "Blob",
Aggregations =
{
MetricAggregationType.Average
},
Granularity = TimeSpan.FromMinutes(5),
TimeRange = new QueryTimeRange(TimeSpan.FromMinutes(10))
});
if (usedCapacityResults.GetRawResponse().Status == StatusCodes.Status200OK)
{
var usedCapacityMetric = usedCapacityResults.Value.Metrics.FirstOrDefault(m => m.Name == "BlobCount" && m.Error == null);
var metricValue = usedCapacityMetric?.TimeSeries.FirstOrDefault();
if (metricValue != null && !metricValue.Values.IsNullOrEmpty())
{
var average = metricValue.Values[0].Average;
if (average != null) blobCount = (decimal)average;
}
}
But nothing gets returned.
Then I have tried to get the supported metric namespace, using this call:
GET https://management.azure.com/{resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview
and the only valid metric seems to be Microsoft.Storage/storageAccounts, that does not have the blob count metric.
Any idea how to read from code the BlobCount value?
There will be also the option to retrieve the list of containers and iterate though it to count the blobs, but this is something I want to avoid.
The working solution, with help from MS support:
This is the solution that was provided to me by MS Support team:
private async Task<decimal> GetStorageAccountBlobCount(MetricsQueryClient metricsClient, string resourceId)
{
var blobCount = (decimal)0.0;
try
{
resourceId = $"{resourceId}/blobServices/default";
var blobCountResult = await metricsClient.QueryResourceAsync(resourceId, new[] { "BlobCount" },
new MetricsQueryOptions
{
MetricNamespace = "Microsoft.Storage/storageAccounts/blobServices",
Aggregations =
{
MetricAggregationType.Average
},
Granularity = TimeSpan.FromHours(1),
TimeRange = new QueryTimeRange(TimeSpan.FromMinutes(60))
});
if (blobCountResult.GetRawResponse().Status == StatusCodes.Status200OK)
{
var blobCountMetric = blobCountResult.Value.Metrics.FirstOrDefault(m => m.Name == "BlobCount" && m.Error == null);
var metricValue = blobCountMetric?.TimeSeries.FirstOrDefault();
if (metricValue != null && !metricValue.Values.IsNullOrEmpty())
{
var average = metricValue.Values[0].Average;
if (average != null) blobCount = (decimal)average;
}
}
}
catch (Exception ex)
{
_logger.LogError($"Error on calculate blob count for {resourceId}", ex);
}
return blobCount;
}

Xamarin.iOS Cannot display photo in Push Notification

I have a Notification Service Extension and an AppGroup. I save a photo from camera in the PCL project and copy it to the App Group Container (shared folder).
In the Notification Service Extension I try to download the photo from the App Group container and attach it to the notification but it just does not display in the notification.
I also cannot debug the Service Extension to see what is going. As far as I know that is not possible currently still in Xamarin unless someone can correct me on that please.
Here is the code:
1.in my PCL I save the photo to the AppGroup when a save button is pressed:
private void ButtonSavePhoto_Clicked(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(photoFilePath))
{
Preferences.Set(AppConstants.CUSTOM_PICTURE_FILE_PATH, photoFilePath);
Preferences.Set(AppConstants.CUSTOM_PHOTO_SET_KEY, true);
if (Device.RuntimePlatform == Device.iOS)
{
bool copiedSuccessfully = DependencyService.Get<IPhotoService>().CopiedFileToAppGroupContainer(photoFilePath);
if (copiedSuccessfully)
{
var customPhotoDestPath = DependencyService.Get<IPhotoService>().GetAppContainerCustomPhotoFilePath();
// save the path of the custom photo in the AppGroup container to pass to Notif Extension Service
DependencyService.Get<IGroupUserPrefs>().SetStringValueForKey("imageAbsoluteString", customPhotoDestPath);
// condition whether to use custom photo in push notification
DependencyService.Get<IGroupUserPrefs>().SetBoolValueForKey("isCustomPhotoSet", true);
}
}
buttonSavePhoto.IsEnabled = false;
}
}
2.in my iOS project, Dependency injection calls this method when pressing save button:
public bool CopiedFileToAppGroupContainer(string filePath)
{
bool success = false;
string suiteName = "group.com.company.appName";
var appGroupContainerUrl = NSFileManager.DefaultManager.GetContainerUrl(suiteName);
var appGroupContainerPath = appGroupContainerUrl.Path;
var directoryNameInAppGroupContainer = Path.Combine(appGroupContainerPath, "Pictures");
var filenameDestPath = Path.Combine(directoryNameInAppGroupContainer, AppConstants.CUSTOM_PHOTO_FILENAME);
try
{
Directory.CreateDirectory(directoryNameInAppGroupContainer);
if (File.Exists(filenameDestPath))
{
File.Delete(filenameDestPath);
}
File.Copy(filePath, filenameDestPath);
success = true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return success;
}
Now the path for the photo in the App Group container is:
/private/var/mobile/Containers/Shared/AppGroup/12F209B9-05E9-470C-9C9F-AA959D940302/Pictures/customphoto.jpg
3.Finally in the Notification Service Extension I try to attach the photo to the push notification:
public override void DidReceiveNotificationRequest(UNNotificationRequest request, Action<UNNotificationContent> contentHandler)
{
ContentHandler = contentHandler;
BestAttemptContent = (UNMutableNotificationContent)request.Content.MutableCopy();
string imgPath;
NSUrl imgUrl;
string notificationBody = BestAttemptContent.Body;
string notifBodyInfo = "unknown";
string suiteName = "group.com.company.appname";
NSUserDefaults groupUserDefaults = new NSUserDefaults(suiteName, NSUserDefaultsType.SuiteName);
string pref1_value = groupUserDefaults.StringForKey("user_preference1");
string[] notificationBodySplitAtDelimiterArray = notificationBody.Split(',');
userPrefRegion = notificationBodySplitAtDelimiterArray[0];
bool isCustomAlertSet = groupUserDefaults.BoolForKey("isCustomAlert");
bool isCustomPhotoSet = groupUserDefaults.BoolForKey("isCustomPhotoSet");
string alarmPath = isCustomAlertSet == true ? "customalert.wav" : "default_alert.m4a";
if (isCustomPhotoSet)
{
// this is the App Group url of the custom photo saved in PCL
imgPath = groupUserDefaults.StringForKey("imageAbsoluteString");
}
else
{
imgPath = null;
}
if (imgPath != null )
{
imgUrl = NSUrl.FromString(imgPath);
}
else
{
imgUrl = null;
}
if (!string.IsNullOrEmpty(pref1_value))
{
if (BestAttemptContent.Body.Contains(pref1_value))
{
if (imgUrl != null)
{
// download the image from the AppGroup Container
var task = NSUrlSession.SharedSession.CreateDownloadTask(imgUrl, (tempFile, response, error) =>
{
if (error != null)
{
ContentHandler(BestAttemptContent);
return;
}
if (tempFile == null)
{
ContentHandler(BestAttemptContent);
return;
}
var cache = NSSearchPath.GetDirectories(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomain.User, true);
var cachesFolder = cache[0];
var guid = NSProcessInfo.ProcessInfo.GloballyUniqueString;
var fileName = guid + "customphoto.jpg";
var cacheFile = cachesFolder + fileName;
var attachmentUrl = NSUrl.CreateFileUrl(cacheFile, false, null);
NSError err = null;
NSFileManager.DefaultManager.Copy(tempFile, attachmentUrl, out err);
if (err != null)
{
ContentHandler(BestAttemptContent);
return;
}
UNNotificationAttachmentOptions options = null;
var attachment = UNNotificationAttachment.FromIdentifier("image", attachmentUrl, options, out err);
if (attachment != null)
{
BestAttemptContent.Attachments = new UNNotificationAttachment[] { attachment };
}
});
task.Resume();
}
BestAttemptContent.Title = "My Custom Title";
BestAttemptContent.Subtitle = "My Custom Subtitle";
BestAttemptContent.Body = "Notification Body";
BestAttemptContent.Sound = UNNotificationSound.GetSound(alarmPath);
}
}
else
{
pref1_value = "error getting extracting user pref";
}
// finally display customized notification
ContentHandler(BestAttemptContent);
}
/private/var/mobile/Containers/Shared/AppGroup/12F209B9-05E9-470C-9C9F-AA959D940302/Pictures/customphoto.jpg
From shared code, when image getting from AppGroup .You can check the file path whether work in this project.
imgPath = groupUserDefaults.StringForKey("imageAbsoluteString");
If not getting file from this path. You can get Url from AppGroup directly.Here is a sample as follow:
var FileManager = new NSFileManager();
var appGroupContainer = FileManager.GetContainerUrl("group.com.company.appName");
NSUrl fileURL = appGroupContainer.Append("customphoto.jpg", false);
And if fileURL can not be directly used, also can convert to NSData and save to local file system. This also can be a try.
Here is a sample below that grabs from local file system:
public static void Sendlocalnotification()
{
var localURL = "...";
NSUrl url = NSUrl.FromString(localURL) ;
var attachmentID = "image";
var options = new UNNotificationAttachmentOptions();
NSError error;
var attachment = UNNotificationAttachment.FromIdentifier(attachmentID, url, options,out error);
var content = new UNMutableNotificationContent();
content.Attachments = new UNNotificationAttachment[] { attachment };
content.Title = "Good Morning ~";
content.Subtitle = "Pull this notification ";
content.Body = "reply some message-BY Ann";
content.CategoryIdentifier = "message";
var trigger1 = UNTimeIntervalNotificationTrigger.CreateTrigger(0.1, false);
var requestID = "messageRequest";
var request = UNNotificationRequest.FromIdentifier(requestID, content, trigger1);
UNUserNotificationCenter.Current.AddNotificationRequest(request, (err) =>
{
if (err != null)
{
Console.Write("Notification Error");
}
});
}

The remote server returned an error: (401) Unauthorized - CSOM - OAuth - after specific time

I am trying to get permissions assigned to each document from the document library using csom in console application. Console app is running fine for almost one hour and post that I get error : "The remote server returned an error: (401) Unauthorized."
I registered an app in SharePoint and I'm using Client ID & Client Secret Key to generate Client Context. Here is the code to get the client context
public static ClientContext getClientContext()
{
ClientContext ctx = null;
try
{
ctx = new AuthenticationManager().GetAppOnlyAuthenticatedContext(SiteUrl, ClientId, ClientSecret);
ctx.RequestTimeout = 6000000;
ctx.ExecutingWebRequest += delegate (object sender, WebRequestEventArgs e)
{
e.WebRequestExecutor.WebRequest.UserAgent = "NONISV|Contoso|GovernanceCheck/1.0";
};
}
catch (Exception ex)
{
WriteLog("Method-getClientContext, Error - " + ex.Message);
}
return ctx;
}
Here is the code which is getting all the assignments assigned to document in document library.
static StringBuilder excelData = new StringBuilder();
static void ProcessWebnLists()
{
try
{
string[] lists = { "ABC", "XYZ", "DEF", "GHI"};
foreach (string strList in lists)
{
using (var ctx = getClientContext())
{
if (ctx != null)
{
Web web = ctx.Site.RootWeb;
ListCollection listCollection = web.Lists;
ctx.Load(web);
ctx.Load(listCollection);
ctx.ExecuteQuery();
List list = web.Lists.GetByTitle(strList);
ctx.Load(list);
ctx.ExecuteQuery();
Console.WriteLine("List Name:{0}", list.Title);
var query = new CamlQuery { ViewXml = "<View/>" };
var items = list.GetItems(query);
ctx.Load(items);
ctx.ExecuteQuery();
foreach (var item in items)
{
var itemAssignments = item.RoleAssignments;
ctx.Load(item, i => i.DisplayName);
ctx.Load(itemAssignments);
ctx.ExecuteQuery();
Console.WriteLine(item.DisplayName);
string groupNames = "";
foreach (var assignment in itemAssignments)
{
try
{
ctx.Load(assignment.Member);
ctx.ExecuteQuery();
groupNames += "[" + assignment.Member.Title.Replace(",", " ") + "] ";
Console.WriteLine($"--> {assignment.Member.Title}");
}
catch (Exception e)
{
WriteLog("Method-ProcessWebnLists, List-" + list.Title + ", Document Title - " + item.DisplayName + ", Error : " + e.Message);
WriteLog("Method-ProcessWebnLists, StackTrace : " + e.StackTrace);
}
}
excelData.AppendFormat("{0},{1},ITEM,{2}", list.Title, (item.DisplayName).Replace(",", " "), groupNames);
excelData.AppendLine();
}
}
excelData.AppendLine();
}
}
}
catch (Exception ex)
{
WriteLog("Method-ProcessWebnLists, Error : " + ex.Message);
WriteLog("Method-ProcessWebnLists, StackTrace : " + ex.StackTrace.ToString());
}
}
Can anyone suggest what else I am missing in this or what kind of changes do I need to make to avoid this error?
The process is taking too much time so I want my application to keep running for couple of hours.
Thanks in advance!
Is there a reason why getClientContext() is inside the foreach loop?
I encountered this error before.
I added this to solve mine.
public static void ExecuteQueryWithIncrementalRetry(this ClientContext context, int retryCount, int delay)
{
int retryAttempts = 0;
int backoffInterval = delay;
if (retryCount <= 0)
throw new ArgumentException("Provide a retry count greater than zero.");
if (delay <= 0)
throw new ArgumentException("Provide a delay greater than zero.");
while (retryAttempts < retryCount)
{
try
{
context.ExecuteQuery();
return;
}
catch (WebException wex)
{
var response = wex.Response as HttpWebResponse;
if (response != null && response.StatusCode == (HttpStatusCode)429)
{
Console.WriteLine(string.Format("CSOM request exceeded usage limits. Sleeping for {0} seconds before retrying.", backoffInterval));
//Add delay.
System.Threading.Thread.Sleep(backoffInterval);
//Add to retry count and increase delay.
retryAttempts++;
backoffInterval = backoffInterval * 2;
}
else
{
throw;
}
}
}
throw new MaximumRetryAttemptedException(string.Format("Maximum retry attempts {0}, have been attempted.", retryCount));
}
Basically, this code backs off for a certain time/interval before executing another query, to prevent throttling.
So instead of using ctx.ExecuteQuery(), use ctx.ExecuteQueryWithIncrementalRetry(5, 1000);
Further explanation here
https://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online

NetSuite SuiteTalk API - Get Inventory Details

I'm using the SuiteTalk (API) service for NetSuite to retrieve a list of Assemblies. I need to load the InventoryDetails fields on the results to view the serial/lot numbers assigned to the items. This is the current code that I'm using, but the results still show those fields to come back as NULL, although I can see the other fields for the AssemblyBuild object. How do I get the inventory details (serials/lot#'s) to return on a transaction search?
public static List<AssemblyBuildResult> Get()
{
var listAssemblyBuilds = new List<AssemblyBuildResult>();
var service = Service.Context();
var ts = new TransactionSearch();
var tsb = new TransactionSearchBasic();
var sfType = new SearchEnumMultiSelectField
{
#operator = SearchEnumMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new string[] { "_assemblyBuild" }
};
tsb.type = sfType;
ts.basic = tsb;
ts.inventoryDetailJoin = new InventoryDetailSearchBasic();
// perform the search
var response = service.search(ts);
response.pageSizeSpecified = true;
// Process response
if (response.status.isSuccess)
{
// Process the records returned in the response
// Get more records with pagination
if (response.totalRecords > 0)
{
for (var x = 1; x <= response.totalPages; x++)
{
var records = response.recordList;
foreach (var t in records)
{
var ab = (AssemblyBuild) t;
listAssemblyBuilds.Add(GetAssemblyBuildsResult(ab));
}
if (response.pageIndex < response.totalPages)
{
response = service.searchMoreWithId(response.searchId, x + 1);
}
}
}
}
// Parse and return NetSuite WorkOrder into assembly WorkOrderResult list
return listAssemblyBuilds;
}
After much pain and suffering, I was able to solve this problem with the following code:
/// <summary>
/// Returns List of AssemblyBuilds from NetSuite
/// </summary>
/// <returns></returns>
public static List<AssemblyBuildResult> Get(string id = "", bool getDetails = false)
{
// Object to populate and return results
var listAssemblyBuilds = new List<AssemblyBuildResult>();
// Initiate Service and SavedSearch (TransactionSearchAdvanced)
var service = Service.Context();
var tsa = new TransactionSearchAdvanced
{
savedSearchScriptId = "customsearch_web_assemblysearchmainlist"
};
// Filter by ID if specified
if (id != "")
{
tsa.criteria = new TransactionSearch()
{
basic = new TransactionSearchBasic()
{
internalId = new SearchMultiSelectField
{
#operator = SearchMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new[] {
new RecordRef() {
type = RecordType.assemblyBuild,
typeSpecified = true,
internalId = id
}
}
}
}
};
}
// Construct custom columns to return
var tsr = new TransactionSearchRow();
var tsrb = new TransactionSearchRowBasic();
var orderIdCols = new SearchColumnSelectField[1];
var orderIdCol = new SearchColumnSelectField();
orderIdCols[0] = orderIdCol;
tsrb.internalId = orderIdCols;
var tranDateCols = new SearchColumnDateField[1];
var tranDateCol = new SearchColumnDateField();
tranDateCols[0] = tranDateCol;
tsrb.tranDate = tranDateCols;
var serialNumberCols = new SearchColumnStringField[1];
var serialNumberCol = new SearchColumnStringField();
serialNumberCols[0] = serialNumberCol;
tsrb.serialNumbers = serialNumberCols;
// Perform the Search
tsr.basic = tsrb;
tsa.columns = tsr;
var response = service.search(tsa);
// Process response
if (response.status.isSuccess)
{
var searchRows = response.searchRowList;
if (searchRows != null && searchRows.Length >= 1)
{
foreach (SearchRow t in searchRows)
{
var transactionRow = (TransactionSearchRow)t;
listAssemblyBuilds.Add(GetAssemblyBuildsResult(transactionRow, getDetails));
}
}
}
// Parse and return NetSuite WorkOrder into assembly WorkOrderResult list
return listAssemblyBuilds;
}
private static string GetAssemblyBuildLotNumbers(string id)
{
var service = Service.Context();
var serialNumbers = "";
var tsa = new TransactionSearchAdvanced
{
savedSearchScriptId = "customsearch_web_assemblysearchlineitems"
};
service.searchPreferences = new SearchPreferences { bodyFieldsOnly = false };
tsa.criteria = new TransactionSearch()
{
basic = new TransactionSearchBasic()
{
internalId = new SearchMultiSelectField
{
#operator = SearchMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new[] {
new RecordRef() {
type = RecordType.assemblyBuild,
typeSpecified = true,
internalId = id
}
}
}
}
};
// Construct custom columns to return
var tsr = new TransactionSearchRow();
var tsrb = new TransactionSearchRowBasic();
var orderIdCols = new SearchColumnSelectField[1];
var orderIdCol = new SearchColumnSelectField();
orderIdCols[0] = orderIdCol;
tsrb.internalId = orderIdCols;
var serialNumberCols = new SearchColumnStringField[1];
var serialNumberCol = new SearchColumnStringField();
serialNumberCols[0] = serialNumberCol;
tsrb.serialNumbers = serialNumberCols;
tsr.basic = tsrb;
tsa.columns = tsr;
var response = service.search(tsa);
if (response.status.isSuccess)
{
var searchRows = response.searchRowList;
if (searchRows != null && searchRows.Length >= 1)
{
foreach (SearchRow t in searchRows)
{
var transactionRow = (TransactionSearchRow)t;
if (transactionRow.basic.serialNumbers != null)
{
return transactionRow.basic.serialNumbers[0].searchValue;
}
}
}
}
return serialNumbers;
}
private static AssemblyBuildResult GetAssemblyBuildsResult(TransactionSearchRow tsr, bool getDetails)
{
if (tsr != null)
{
var assemblyInfo = new AssemblyBuildResult
{
NetSuiteId = tsr.basic.internalId[0].searchValue.internalId,
ManufacturedDate = tsr.basic.tranDate[0].searchValue,
SerialNumbers = tsr.basic.serialNumbers[0].searchValue
};
// If selected, this will do additional NetSuite queries to get detailed data (slower)
if (getDetails)
{
// Look up Lot Number
assemblyInfo.LotNumber = GetAssemblyBuildLotNumbers(tsr.basic.internalId[0].searchValue.internalId);
}
return assemblyInfo;
}
return null;
}
What I learned about pulling data from NetSuite:
Using SavedSearches is the best method to pull data that doesn't automatically come through in the API objects
It is barely supported
Don't specify an ID on the SavedSearch, specify a criteria in the TransactionSearch to get one record
You will need to specify which columns to actually pull down. NetSuite doesn't just send you the data from a SavedSearch automatically
You cannot view data in a SavedSearch that contains a Grouping
In the Saved Search, use the Criteria Main Line = true/false to read data from the main record (top of UI screen), and line items (bottom of screen)

Azure Web App staging monitoring

everyone.
Right now I'm using nice nuGet package to monitor my azure web app. The code could look something like this
var certificate = BuildCertificate(StoreName.My, StoreLocation.LocalMachine, " certificate thumbprint");
string subscriptionId = "subscriptionId";
string webspace = "westuswebspace";
string website = "websitename";
using (var client = new MetricsClient(new CertificateCloudCredentials(subscriptionId, certificate)))
{
var metricDefs = client.MetricDefinitions.List(ResourceIdBuilder.BuildWebSiteResourceId(webspace, website), null, null);
var end = DateTime.UtcNow;
var start = end.AddHours(-12);
var timeGrain = TimeSpan.FromHours(1);
var metricVals = client.MetricValues.List(ResourceIdBuilder.BuildWebSiteResourceId(webspace, website),
metricDefs.MetricDefinitionCollection.Value.Select(md => md.DisplayName).ToList(), "",
timeGrain, start, end);
foreach (var metric in metricVals.MetricValueSetCollection.Value)
{
Console.WriteLine("Metric Name: {0}", metric.DisplayName);
Console.WriteLine("Metric Namespace: {0}", metric.Namespace);
foreach (var val in metric.MetricValues)
{
Console.WriteLine("Value: {0}", val.Average.HasValue ? val.Average.Value : 0);
}
Console.WriteLine("//////////////////////");
}
}
certificate retrieval logic
private static X509Certificate2 BuildCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint)
{
var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
try
{
var builder = new StringBuilder(thumbprint.Length);
foreach (char c in thumbprint.Where(char.IsLetterOrDigit))
{
builder.Append(c);
}
string cleanThumbprint = builder.ToString();
X509Certificate2Collection list = store.Certificates.Find(X509FindType.FindByThumbprint, cleanThumbprint, false);
X509Certificate2 cert;
if (list == null || list.Count != 1)
{
cert = null;
}
else
{
cert = list[0];
}
return cert;
}
finally
{
store.Close();
}
}
so, everything works perfectly fine, when I'm working with prod, but I can't figure out, how I can monitor my staging environment.
Please, help me with that. I would highly appreciate any help.

Resources