Webinsights - Sending Diagnostics Data from Microservice to Diagnostics Backend (#236)

* Adding customer insights for different simulation events (start, stop, error)
This commit is contained in:
sasathy 2018-09-11 15:40:24 -07:00 коммит произвёл Harleen Thind
Родитель a290ef3027
Коммит 27ddbbeb67
24 изменённых файлов: 437 добавлений и 72 удалений

Просмотреть файл

@ -19,16 +19,19 @@ namespace Services.Test
private readonly Mock<IStorageAdapterClient> storage;
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
private readonly CustomDeviceModels target;
public CustomDeviceModelsTest()
{
this.storage = new Mock<IStorageAdapterClient>();
this.logger = new Mock<ILogger>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.target = new CustomDeviceModels(
this.storage.Object,
this.logger.Object);
this.logger.Object,
this.diagnosticsLogger.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]

Просмотреть файл

@ -26,6 +26,7 @@ namespace Services.Test
private readonly Mock<IRegistryManager> registry;
private readonly Mock<IDeviceClientWrapper> deviceClient;
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
public DevicesTest(ITestOutputHelper log)
{
@ -36,13 +37,15 @@ namespace Services.Test
this.registry = new Mock<IRegistryManager>();
this.deviceClient = new Mock<IDeviceClientWrapper>();
this.logger = new Mock<ILogger>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.target = new Devices(
this.config.Object,
this.connectionStringManager.Object,
this.registry.Object,
this.deviceClient.Object,
this.logger.Object);
this.logger.Object,
this.diagnosticsLogger.Object);
this.connectionStringManager
.Setup(x => x.GetIotHubConnectionString())

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Moq;
using Services.Test.helpers;
using Xunit;
namespace Services.Test.Diagnostics
{
public class DiagnosticsLoggerTest
{
private const string DIAGNOSTICS_SERVICE_URL = @"http://diagnostics";
private readonly Mock<IHttpClient> mockHttpClient;
private DiagnosticsLogger target;
public DiagnosticsLoggerTest()
{
this.mockHttpClient = new Mock<IHttpClient>();
this.target = new DiagnosticsLogger(
this.mockHttpClient.Object,
new ServicesConfig
{
DiagnosticsEndpointUrl = DIAGNOSTICS_SERVICE_URL
});
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ShouldLogServiceStart()
{
// Act
target.LogServiceStartAsync("test").Wait(Constants.TEST_TIMEOUT);
// Assert - Checking if the httpcall is made just once
this.mockHttpClient.Verify(x => x.PostAsync(It.IsAny<HttpRequest>()), Times.Once);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ShouldLogServiceHeartbeat()
{
// Act
target.LogServiceHeartbeatAsync().Wait(Constants.TEST_TIMEOUT);
// Assert - Checking if the httpcall is made just once
this.mockHttpClient.Verify(x => x.PostAsync(It.IsAny<HttpRequest>()), Times.Once);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ShouldLogServiceError()
{
// Act
// Logging service error sending just a message string
target.LogServiceErrorAsync("testmessage").Wait(Constants.TEST_TIMEOUT);
// Logging service error along with an exception
target.LogServiceErrorAsync("testmessage", new System.Exception().Message).Wait(Constants.TEST_TIMEOUT);
// Logging service error along with an object
target.LogServiceErrorAsync("testmessage", new { Test = "test" }).Wait(Constants.TEST_TIMEOUT);
// Assert - Checking if the httpcall is made exactly 3 times one for each type of service error
this.mockHttpClient.Verify(x => x.PostAsync(It.IsAny<HttpRequest>()), Times.Exactly(3));
}
}
}

Просмотреть файл

@ -13,14 +13,16 @@ namespace Services.Test
public class IotHubConnectionStringManagerTest
{
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
private readonly IServicesConfig config;
private readonly IotHubConnectionStringManager target;
public IotHubConnectionStringManagerTest()
{
this.logger = new Mock<ILogger>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.config = new ServicesConfig();
this.target = new IotHubConnectionStringManager(this.config, this.logger.Object);
this.target = new IotHubConnectionStringManager(this.config, this.diagnosticsLogger.Object, this.logger.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]

Просмотреть файл

@ -29,6 +29,7 @@ namespace Services.Test
private readonly Mock<IStorageAdapterClient> storage;
private readonly Mock<IDevices> devices;
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
private readonly Mock<IIotHubConnectionStringManager> connStringManager;
private readonly Simulations target;
private readonly List<DeviceModel> models;
@ -40,6 +41,7 @@ namespace Services.Test
this.deviceModels = new Mock<IDeviceModels>();
this.storage = new Mock<IStorageAdapterClient>();
this.logger = new Mock<ILogger>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.devices = new Mock<IDevices>();
this.connStringManager = new Mock<IIotHubConnectionStringManager>();
this.models = new List<DeviceModel>
@ -50,7 +52,7 @@ namespace Services.Test
new DeviceModel { Id = "AA" }
};
this.target = new Simulations(this.deviceModels.Object, this.storage.Object, this.connStringManager.Object, this.devices.Object, this.logger.Object);
this.target = new Simulations(this.deviceModels.Object, this.storage.Object, this.connStringManager.Object, this.devices.Object, this.logger.Object, this.diagnosticsLogger.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]

Просмотреть файл

@ -17,6 +17,7 @@ namespace Services.Test
public class StockDeviceModelTest
{
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
private readonly Mock<IServicesConfig> config;
private readonly StockDeviceModels target;
@ -24,10 +25,12 @@ namespace Services.Test
{
this.logger = new Mock<ILogger>();
this.config = new Mock<IServicesConfig>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.target = new StockDeviceModels(
this.config.Object,
this.logger.Object);
this.logger.Object,
this.diagnosticsLogger.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]

Просмотреть файл

@ -45,13 +45,16 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly IStorageAdapterClient storage;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
public CustomDeviceModels(
IStorageAdapterClient storage,
ILogger logger)
ILogger logger,
IDiagnosticsLogger diagnosticsLogger)
{
this.storage = storage;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
}
/// <summary>
@ -67,8 +70,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Unable to load device models from storage", e);
throw new ExternalDependencyException("Unable to load device models from storage", e);
var msg = "Unable to load device models from storage";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new ExternalDependencyException(msg, e);
}
try
@ -86,8 +91,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Unable to parse device models loaded from storage", e);
throw new ExternalDependencyException("Unable to parse device models loaded from storage", e);
var msg = "Unable to parse device models loaded from storage";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new ExternalDependencyException(msg, e);
}
}
@ -113,9 +120,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Unable to load device model from storage",
var msg = "Unable to load device model from storage";
this.log.Error(msg,
() => new { id, e.Message, Exception = e });
throw new ExternalDependencyException("Unable to load device model from storage", e);
this.diagnosticsLogger.LogServiceErrorAsync(msg,
new { id, e.Message, Exception = e.Message });
throw new ExternalDependencyException(msg, e);
}
try
@ -161,10 +171,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Failed to insert new device model into storage",
var msg = "Failed to insert new device model into storage";
this.log.Error(msg,
() => new { deviceModel, generateId, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg,
new { deviceModel, generateId, e.Message });
throw new ExternalDependencyException(
"Failed to insert new device model into storage", e);
msg, e);
}
return deviceModel;
@ -201,8 +214,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
else
{
this.log.Error("Invalid ETag.", () => new { CurrentETag = item.ETag, ETagProvided = eTag });
throw new ConflictingResourceException("Invalid ETag. Device Model ETag is:'" + item.ETag + "'.");
var msg = "Invalid ETag.";
this.log.Error(msg, () => new { CurrentETag = item.ETag, ETagProvided = eTag });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { CurrentETag = item.ETag, ETagProvided = eTag });
throw new ConflictingResourceException(msg + "Device Model ETag is:'" + item.ETag + "'.");
}
}
catch (ConflictingResourceException)
@ -218,7 +233,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception exception)
{
this.log.Error("Something went wrong while upserting the device model.", () => new { deviceModel });
var msg = "Something went wrong while upserting the device model.";
this.log.Error(msg, () => new { deviceModel });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { deviceModel });
throw new ExternalDependencyException("Failed to upsert: " + exception.Message, exception);
}
@ -236,7 +253,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Something went wrong while deleting the device model.", () => new { id, e });
var msg = "Something went wrong while deleting the device model.";
this.log.Error(msg, () => new { id, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { id, e.Message });
throw new ExternalDependencyException("Failed to delete the device model", e);
}
}

Просмотреть файл

@ -26,6 +26,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
private readonly IDeviceClientWrapper client;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
private readonly IScriptInterpreter scriptInterpreter;
private IDictionary<string, Script> cloudToDeviceMethods;
private ISmartDictionary deviceState;
@ -36,10 +37,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
public DeviceMethods(
IDeviceClientWrapper client,
ILogger logger,
IDiagnosticsLogger diagnosticsLogger,
IScriptInterpreter scriptInterpreter)
{
this.client = client;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
this.scriptInterpreter = scriptInterpreter;
this.deviceId = string.Empty;
this.isRegistered = false;
@ -106,7 +109,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Failed executing method.", () => new { methodRequest, e });
var msg = "Failed executing method.";
this.log.Error(msg, () => new { methodRequest, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { methodRequest, e.Message });
return Task.FromResult(new MethodResponse((int) HttpStatusCode.InternalServerError));
}
}
@ -151,7 +156,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Error while executing method for device",
var msg = "Error while executing method for device";
this.log.Error(msg,
() => new
{
this.deviceId,
@ -159,6 +165,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
methodRequest.DataAsJson,
e
});
this.diagnosticsLogger.LogServiceErrorAsync(msg,
new
{
this.deviceId,
methodName = methodRequest.Name,
methodRequest.DataAsJson,
e.Message
});
}
}

Просмотреть файл

@ -75,6 +75,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly IIotHubConnectionStringManager connectionStringManager;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
private readonly IServicesConfig config;
private readonly IDeviceClientWrapper deviceClient;
@ -90,7 +91,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
IIotHubConnectionStringManager connStringManager,
IRegistryManager registryManager,
IDeviceClientWrapper deviceClient,
ILogger logger)
ILogger logger,
IDiagnosticsLogger diagnosticsLogger)
{
this.config = config;
this.connectionStringManager = connStringManager;
@ -98,6 +100,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
this.registry = registryManager;
this.deviceClient = deviceClient;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
this.twinReadsWritesEnabled = config.TwinReadWriteEnabled;
this.setupDone = false;
}
@ -129,7 +132,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("IoT Hub connection setup failed", e);
var msg = "IoT Hub connection setup failed";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw;
}
}
@ -140,7 +145,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
this.CheckSetup();
var sdkClient = this.GetDeviceSdkClient(device, protocol);
var methods = new DeviceMethods(sdkClient, this.log, scriptInterpreter);
var methods = new DeviceMethods(sdkClient, this.log, this.diagnosticsLogger, scriptInterpreter);
return new DeviceClient(
device.Id,
@ -192,8 +197,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
catch (Exception e)
{
var timeSpentMsecs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - start;
this.log.Error("Unable to fetch the IoT device", () => new { timeSpentMsecs, deviceId, e });
throw new ExternalDependencyException("Unable to fetch the IoT device");
var msg = "Unable to fetch the IoT device";
this.log.Error(msg, () => new { timeSpentMsecs, deviceId, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { timeSpentMsecs, deviceId, e.Message });
throw new ExternalDependencyException(msg);
}
return result;
@ -217,8 +224,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
catch (Exception e)
{
var timeSpentMsecs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - start;
this.log.Error("Unable to create the device", () => new { timeSpentMsecs, deviceId, e });
throw new ExternalDependencyException("Unable to create the device", e);
var msg = "Unable to create the device";
this.log.Error(msg, () => new { timeSpentMsecs, deviceId, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { timeSpentMsecs, deviceId, e.Message });
throw new ExternalDependencyException(msg, e);
}
}
@ -333,17 +342,23 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (TooManyDevicesException error)
{
this.log.Error("Failed to delete devices, the batch is too big", error);
var msg = "Failed to delete devices, the batch is too big";
this.log.Error(msg, error);
this.diagnosticsLogger.LogServiceErrorAsync(msg, error.Message);
throw;
}
catch (IotHubCommunicationException error)
{
this.log.Error("Failed to delete devices (IotHubCommunicationException)", () => new { error.InnerException, error });
var msg = "Failed to delete devices (IotHubCommunicationException)";
this.log.Error(msg, () => new { error.InnerException, error });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { error.Message });
throw;
}
catch (Exception error)
{
this.log.Error("Failed to delete devices", error);
var msg = "Failed to delete devices";
this.log.Error(msg, error);
this.diagnosticsLogger.LogServiceErrorAsync(msg, error.Message); ;
throw;
}
}

Просмотреть файл

@ -0,0 +1,132 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics
{
public interface IDiagnosticsLogger
{
Task LogServiceStartAsync(string message);
Task LogServiceHeartbeatAsync();
Task LogServiceErrorAsync(
string message,
string exceptionMessage = "",
[CallerMemberName] string callerName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0);
Task LogServiceErrorAsync(
string message,
object data,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0);
}
public class DiagnosticsLogger : IDiagnosticsLogger
{
public struct JsonStruct
{
[JsonProperty(PropertyName = "EventType")]
public string EventType;
[JsonProperty(PropertyName = "EventProperties")]
public Dictionary<string, object> EventProperties;
public JsonStruct(string eventType, Dictionary<string, object> eventProps)
{
this.EventType = eventType;
this.EventProperties = eventProps;
}
}
private readonly IHttpClient httpClient;
private readonly IServicesConfig servicesConfig;
private readonly string diagnosticsEndpoint = string.Empty;
private const string SERVICE_ERROR_EVENT = "ServiceError";
private const string SERVICE_START_EVENT = "ServiceStart";
private const string SERVICE_HEARTBEAT_EVENT = "ServiceHeartbeat";
public DiagnosticsLogger(IHttpClient httpClient, IServicesConfig servicesConfig)
{
this.httpClient = httpClient;
this.servicesConfig = servicesConfig;
this.diagnosticsEndpoint = this.servicesConfig.DiagnosticsEndpointUrl;
}
public async Task LogServiceStartAsync(string message)
{
JsonStruct jsonStruct = new JsonStruct(SERVICE_START_EVENT + message, null);
await this.httpClient.PostAsync(this.PrepareRequest(this.diagnosticsEndpoint, jsonStruct));
}
public async Task LogServiceHeartbeatAsync()
{
JsonStruct jsonStruct = new JsonStruct(SERVICE_HEARTBEAT_EVENT, null);
await this.httpClient.PostAsync(this.PrepareRequest(this.diagnosticsEndpoint, jsonStruct));
}
public async Task LogServiceErrorAsync(
string message,
string exceptionMessage = "",
[CallerMemberName] string callerName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
JsonStruct jsonStruct = this.ConvertServiceErrorToJson(message, exceptionMessage, null, callerName, filePath, lineNumber);
await this.httpClient.PostAsync(this.PrepareRequest(this.diagnosticsEndpoint, jsonStruct));
}
public async Task LogServiceErrorAsync(
string message,
object data,
[CallerMemberName] string callerName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
JsonStruct jsonStruct = this.ConvertServiceErrorToJson(message, "", data, callerName, filePath, lineNumber);
await this.httpClient.PostAsync(this.PrepareRequest(this.diagnosticsEndpoint, jsonStruct));
}
private JsonStruct ConvertServiceErrorToJson(string message,
string exceptionMessage,
object data,
string callerName = "",
string filePath = "",
int lineNumber = 0)
{
var eventProps = new Dictionary<string, object>
{
{ "message", message + $"(CallerMemberName = '{callerName}', CallerFilePath = '{filePath}', lineNumber = '{lineNumber}')" }
};
if (!string.IsNullOrEmpty(exceptionMessage))
{
eventProps.Add("ExceptionMessage", exceptionMessage);
}
if (data != null)
{
eventProps.Add("data", data);
}
return new JsonStruct(SERVICE_ERROR_EVENT, eventProps);
}
private HttpRequest PrepareRequest(string path, object obj = null)
{
var request = new HttpRequest();
request.SetUriFromString(path);
request.SetContent(obj);
return request;
}
}
}

Просмотреть файл

@ -33,14 +33,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
private readonly IServicesConfig config;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
public IotHubConnectionStringManager(
IServicesConfig config,
IDiagnosticsLogger diagnosticsLogger,
ILogger logger)
{
this.config = config;
this.connStringFilePath = config.IoTHubDataFolder + CONNSTRING_FILE_NAME;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
}
/// <summary>
@ -139,6 +142,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
"The correct format is: HostName=[hubname];SharedAccessKeyName=" +
"[iothubowner or service];SharedAccessKey=[null or valid key]";
this.log.Error(message);
this.diagnosticsLogger.LogServiceErrorAsync(message);
throw new InvalidIotHubConnectionStringFormatException(message);
}
@ -177,10 +181,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
string message = "Could not connect to IotHub with the connection " +
var message = "Could not connect to IotHub with the connection " +
"string provided. Check that the key is valid and " +
"that the hub exists.";
this.log.Error(message, e);
this.diagnosticsLogger.LogServiceErrorAsync(message, e.Message);
throw new IotHubConnectionException(message, e);
}
}
@ -195,10 +200,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
string message = "Could not read devices with the Iot Hub connection " +
var message = "Could not read devices with the Iot Hub connection " +
"string provided. Check that the policy for the key allows " +
"`Registry Read/Write` and `Service Connect` permissions.";
this.log.Error(message, e);
this.diagnosticsLogger.LogServiceErrorAsync(message, e.Message);
throw new IotHubConnectionException(message, e);
}
}
@ -217,10 +223,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
string message = "Could not create devices with the Iot Hub connection " +
var message = "Could not create devices with the Iot Hub connection " +
"string provided. Check that the policy for the key allows " +
"`Registry Read/Write` and `Service Connect` permissions.";
this.log.Error(message, e);
this.diagnosticsLogger.LogServiceErrorAsync(message, e.Message);
throw new IotHubConnectionException(message, e);
}
@ -237,9 +244,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
string message = "Could not delete test device from IotHub. Attempt " +
var message = "Could not delete test device from IotHub. Attempt " +
deleteRetryCount + 1 + " of " + MAX_DELETE_RETRY;
this.log.Error(message, () => new { testDeviceId, e });
this.diagnosticsLogger.LogServiceErrorAsync(message, new { testDeviceId, e.Message });
throw new IotHubConnectionException(message, e);
}
@ -250,8 +258,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
if (response != null)
{
string message = "Could not delete test device from IotHub.";
var message = "Could not delete test device from IotHub.";
this.log.Error(message, () => new { testDeviceId });
this.diagnosticsLogger.LogServiceErrorAsync(message, new { testDeviceId });
throw new IotHubConnectionException(message);
}
}
@ -271,9 +280,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
string msg = "Unable to use default IoT Hub. Check that the " +
var msg = "Unable to use default IoT Hub. Check that the " +
"pre-provisioned hub exists and has the correct permissions.";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new IotHubConnectionException(msg, e);
}
@ -284,8 +294,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
this.log.Error("Unable to delete connection string file.",
var msg = "Unable to delete connection string file";
this.log.Error(msg,
() => new { this.connStringFilePath, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg,
new { this.connStringFilePath, e.Message });
throw;
}
}
@ -338,8 +351,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
this.log.Error("Unable to write connection string to file.",
var msg = "Unable to write connection string to file";
this.log.Error(msg,
() => new { this.connStringFilePath, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg,
new { this.connStringFilePath, e.Message });
throw;
}
}
@ -361,8 +377,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub
}
catch (Exception e)
{
this.log.Error("Unable to read connection string from file.",
var message = "Unable to read connection string from file";
this.log.Error(message,
() => new { this.connStringFilePath, e });
this.diagnosticsLogger.LogServiceErrorAsync(message,
new { this.connStringFilePath, e.Message });
return null;
}
}

Просмотреть файл

@ -20,6 +20,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
StorageConfig SimulationsStorage { get; set; }
StorageConfig DevicesStorage { get; set; }
StorageConfig PartitionsStorage { get; set; }
string DiagnosticsEndpointUrl { get; }
}
// TODO: test Windows/Linux folder separator
@ -65,6 +66,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
public int StorageAdapterApiTimeout { get; set; }
public string DiagnosticsEndpointUrl { get; set; }
public bool TwinReadWriteEnabled { get; set; }
public StorageConfig MainStorage { get; set; }

Просмотреть файл

@ -69,19 +69,22 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly IIotHubConnectionStringManager connectionStringManager;
private readonly IDevices devices;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
public Simulations(
IDeviceModels deviceModels,
IStorageAdapterClient storage,
IIotHubConnectionStringManager connectionStringManager,
IDevices devices,
ILogger logger)
ILogger logger,
IDiagnosticsLogger diagnosticsLogger)
{
this.deviceModels = deviceModels;
this.storage = storage;
this.connectionStringManager = connectionStringManager;
this.devices = devices;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
}
/// <summary>
@ -209,8 +212,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
if (simulation.ETag != existingSimulation.ETag)
{
this.log.Error("Invalid ETag. Running simulation ETag is:'", () => new { simulation });
throw new ResourceOutOfDateException("Invalid ETag. Running simulation ETag is:'" + simulation.ETag + "'.");
var msg = "Invalid ETag. Running simulation ETag is:'";
this.log.Error(msg, () => new { simulation });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { simulation.Id, simulation.Name });
throw new ResourceOutOfDateException(msg + simulation.ETag + "'.");
}
simulation.Created = existingSimulation.Created;

Просмотреть файл

@ -24,16 +24,19 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly IServicesConfig config;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
private List<string> deviceModelFiles;
private List<DeviceModel> deviceModels;
public StockDeviceModels(
IServicesConfig config,
ILogger logger)
ILogger logger,
IDiagnosticsLogger diagnosticsLogger)
{
this.config = config;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
this.deviceModelFiles = null;
this.deviceModels = null;
}
@ -52,8 +55,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Unable to load Device Model files", e);
var msg = "Unable to load Device Model files";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw;
}
@ -68,8 +72,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
}
catch (Exception e)
{
this.log.Error("Unable to parse Device Model files", e);
throw new InvalidConfigurationException("Unable to parse Device Model files: " + e.Message, e);
var msg = "Unable to parse Device Model files";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new InvalidConfigurationException(msg + e.Message, e);
}
return this.deviceModels;

Просмотреть файл

@ -33,18 +33,21 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter
private readonly IHttpClient httpClient;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
private readonly string serviceUri;
private readonly int timeout;
public StorageAdapterClient(
IHttpClient httpClient,
IServicesConfig config,
ILogger logger)
ILogger logger,
IDiagnosticsLogger diagnosticsLogger)
{
this.httpClient = httpClient;
this.log = logger;
this.serviceUri = config.StorageAdapterApiUrl;
this.timeout = config.StorageAdapterApiTimeout;
this.diagnosticsLogger = diagnosticsLogger;
}
public async Task<Tuple<bool, string>> PingAsync()
@ -68,7 +71,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter
}
catch (Exception e)
{
this.log.Error("Storage adapter check failed", e);
var msg= "Storage adapter check failed";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
message = e.Message;
}

Просмотреть файл

@ -27,6 +27,7 @@ namespace SimulationAgent.Test
private readonly Mock<IRateLimitingConfig> ratingConfig;
private readonly Mock<IConcurrencyConfig> concurrencyConfig;
private readonly Mock<ILogger> logger;
private readonly Mock<IDiagnosticsLogger> diagnosticsLogger;
private readonly Mock<IDeviceModels> deviceModels;
private readonly Mock<IDeviceModelsGeneration> deviceModelsOverriding;
private readonly Mock<IDevices> devices;
@ -44,6 +45,7 @@ namespace SimulationAgent.Test
this.ratingConfig = new Mock<IRateLimitingConfig>();
this.concurrencyConfig = new Mock<IConcurrencyConfig>();
this.logger = new Mock<ILogger>();
this.diagnosticsLogger = new Mock<IDiagnosticsLogger>();
this.deviceModels = new Mock<IDeviceModels>();
this.deviceModelsOverriding = new Mock<IDeviceModelsGeneration>();
this.devices = new Mock<IDevices>();
@ -61,6 +63,7 @@ namespace SimulationAgent.Test
this.rateLimiting.Object,
this.concurrencyConfig.Object,
this.logger.Object,
this.diagnosticsLogger.Object,
this.deviceModels.Object,
this.deviceModelsOverriding.Object,
this.devices.Object,

Просмотреть файл

@ -24,18 +24,22 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
public class Agent : ISimulationAgent
{
private const int CHECK_INTERVAL_MSECS = 10000;
private const int DIAGNOSTICS_POLLING_FREQUENCY_DAYS = 1;
private readonly ILogger log;
private readonly IDiagnosticsLogger logDiagnostics;
private readonly ISimulations simulations;
private readonly ISimulationRunner runner;
private readonly IRateLimiting rateReporter;
private readonly IDeviceModels deviceModels;
private Simulation simulation;
private readonly IDevices devices;
private DateTimeOffset lastPolledTime;
private Simulation simulation;
private bool running;
public Agent(
ILogger logger,
IDiagnosticsLogger diagnosticsLogger,
ISimulations simulations,
ISimulationRunner runner,
IRateLimiting rateReporter,
@ -43,12 +47,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
IDevices devices)
{
this.log = logger;
this.logDiagnostics = diagnosticsLogger;
this.simulations = simulations;
this.runner = runner;
this.rateReporter = rateReporter;
this.deviceModels = deviceModels;
this.devices = devices;
this.running = true;
this.lastPolledTime = DateTimeOffset.UtcNow;
}
public async Task RunAsync()
@ -59,6 +65,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
while (this.running)
{
var oldSimulation = this.simulation;
this.SendSolutionHeartbeatAsync();
try
{
this.log.Debug("------ Checking for simulation changes ------");
@ -198,6 +207,19 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
}
private void SendSolutionHeartbeatAsync()
{
DateTimeOffset now = DateTimeOffset.UtcNow;
TimeSpan duration = now - this.lastPolledTime;
// Send heartbeat every 24 hours
if (duration.Days >= DIAGNOSTICS_POLLING_FREQUENCY_DAYS)
{
this.lastPolledTime = now;
this.logDiagnostics.LogServiceHeartbeatAsync();
}
}
private void CheckForNewSimulation(Simulation newSimulation)
{
if (newSimulation != null && this.simulation == null)
@ -206,8 +228,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
if (this.simulation.ShouldBeRunning())
{
this.log.Debug("------ Starting new simulation ------", () => new { this.simulation });
this.logDiagnostics.LogServiceStartAsync("Starting new simulation");
this.runner.Start(this.simulation);
this.log.Debug("------ New simulation started ------", () => new { this.simulation });
this.logDiagnostics.LogServiceStartAsync("New simulation started");
}
}
}

Просмотреть файл

@ -4,12 +4,14 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection;
@ -41,6 +43,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
// Application logger
private readonly ILogger log;
// Diagnostics logger
private readonly IDiagnosticsLogger diagnosticsLogger;
// Settings to optimize scheduling
private readonly ConnectionLoopSettings connectionLoopSettings;
@ -109,6 +114,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
IRateLimiting rateLimiting,
IConcurrencyConfig concurrencyConfig,
ILogger logger,
IDiagnosticsLogger diagnosticsLogger,
IDeviceModels deviceModels,
IDeviceModelsGeneration deviceModelsOverriding,
IDevices devices,
@ -120,6 +126,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
this.concurrencyConfig = concurrencyConfig;
this.log = logger;
this.diagnosticsLogger = diagnosticsLogger;
this.deviceModels = deviceModels;
this.deviceModelsOverriding = deviceModelsOverriding;
this.devices = devices;
@ -176,9 +183,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (Exception e)
{
var msg = "Failed to create devices";
this.running = false;
this.starting = false;
this.log.Error("Failed to create devices", e);
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
this.IncrementSimulationErrorsCount();
// Return and retry
@ -205,13 +214,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (ResourceNotFoundException)
{
var msg = "The device model doesn't exist";
this.IncrementSimulationErrorsCount();
this.log.Error("The device model doesn't exist", () => new { model.Id });
this.log.Error(msg, () => new { model.Id });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { model.Id });
}
catch (Exception e)
{
var msg = "Unexpected error preparing the device model";
this.IncrementSimulationErrorsCount();
this.log.Error("Unexpected error preparing the device model", () => new { model.Id, e });
this.log.Error(msg, () => new { model.Id, e });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { model.Id, e.Message });
}
}
@ -627,9 +640,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (Exception e)
{
var msg = "Unable to start the telemetry threads";
this.IncrementSimulationErrorsCount();
this.log.Error("Unable to start the telemetry threads", e);
throw new Exception("Unable to start the telemetry threads", e);
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new Exception(msg, e);
}
}
@ -642,9 +657,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (Exception e)
{
var msg = "Unable to start the device connection thread";
this.IncrementSimulationErrorsCount();
this.log.Error("Unable to start the device connection thread", e);
throw new Exception("Unable to start the device connection thread", e);
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new Exception(msg, e);
}
}
@ -657,9 +674,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (Exception e)
{
var msg = "Unable to start the device state thread";
this.IncrementSimulationErrorsCount();
this.log.Error("Unable to start the device state thread", e);
throw new Exception("Unable to start the device state thread", e);
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new Exception(msg, e);
}
}
@ -672,9 +691,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
}
catch (Exception e)
{
var msg = "Unable to start the device properties thread";
this.IncrementSimulationErrorsCount();
this.log.Error("Unable to start the device properties thread", e);
throw new Exception("Unable to start the device properties thread", e);
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
throw new Exception(msg, e);
}
}

Просмотреть файл

@ -40,7 +40,6 @@ namespace WebService.Test.v1.Controllers
this.simulations = new Mock<ISimulations>();
this.logger = new Mock<ILogger>();
this.servicesConfig = new Mock<IServicesConfig>();
this.deploymentConfig = new Mock<IDeploymentConfig>();
this.connectionStringManager = new Mock<IIotHubConnectionStringManager>();
this.simulationRunner = new Mock<ISimulationRunner>();
this.rateReporter = new Mock<IRateLimiting>();

Просмотреть файл

@ -50,6 +50,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
private readonly IConfigurationManager<OpenIdConnectConfiguration> openIdCfgMan;
private readonly IClientAuthConfig config;
private readonly ILogger log;
private readonly IDiagnosticsLogger diagnosticsLogger;
private TokenValidationParameters tokenValidationParams;
private readonly bool authRequired;
private bool tokenValidationInitialized;
@ -59,12 +60,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
RequestDelegate requestDelegate, // Required by ASP.NET
IConfigurationManager<OpenIdConnectConfiguration> openIdCfgMan,
IClientAuthConfig config,
ILogger log)
ILogger log,
IDiagnosticsLogger diagnosticsLogger)
{
this.requestDelegate = requestDelegate;
this.openIdCfgMan = openIdCfgMan;
this.config = config;
this.log = log;
this.diagnosticsLogger = diagnosticsLogger;
this.authRequired = config.AuthRequired;
this.tokenValidationInitialized = false;
@ -137,7 +140,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
}
else
{
this.log.Error("Authorization header not found");
var msg = "Authorization header not found";
this.log.Error(msg);
this.diagnosticsLogger.LogServiceErrorAsync(msg);
}
if (header != null && header.StartsWith(AUTH_HEADER_PREFIX))
@ -146,7 +151,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
}
else
{
this.log.Error("Authorization header prefix not found");
var msg = "Authorization header prefix not found";
this.log.Error(msg);
this.diagnosticsLogger.LogServiceErrorAsync(msg);
}
if (this.ValidateToken(token, context) || !this.authRequired)
@ -183,11 +190,15 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
return true;
}
this.log.Error("JWT token signature algorithm is not allowed.", () => new { jwtToken.SignatureAlgorithm });
var msg = "JWT token signature algorithm is not allowed.";
this.log.Error(msg, () => new { jwtToken.SignatureAlgorithm });
this.diagnosticsLogger.LogServiceErrorAsync(msg, new { jwtToken.SignatureAlgorithm });
}
catch (Exception e)
{
this.log.Error("Failed to validate JWT token", e);
var msg = "Failed to validate JWT token";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
}
return false;
@ -226,7 +237,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth
}
catch (Exception e)
{
this.log.Error("Failed to setup OpenId Connect", e);
var msg = "Failed to setup OpenId Connect";
this.log.Error(msg, e);
this.diagnosticsLogger.LogServiceErrorAsync(msg, e.Message);
}
return this.tokenValidationInitialized;

Просмотреть файл

@ -115,6 +115,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService
builder.RegisterType<DeviceModels>().As<IDeviceModels>().SingleInstance();
builder.RegisterType<Services.Devices>().As<IDevices>().SingleInstance();
builder.RegisterType<RateLimiting>().As<IRateLimiting>().SingleInstance();
builder.RegisterType<DiagnosticsLogger>().As<IDiagnosticsLogger>().SingleInstance();
// The simulation runner contains the service counters, which are read and
// written by multiple parts of the application, so we need to make sure

Просмотреть файл

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth;
@ -91,6 +92,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
private const string LOGGING_WHITELIST_SOURCES_KEY = LOGGING_KEY + "WhiteListSources";
private const string LOGGING_EXTRADIAGNOSTICS_KEY = LOGGING_KEY + "ExtraDiagnostics";
private const string LOGGING_EXTRADIAGNOSTICSPATH_KEY = LOGGING_KEY + "ExtraDiagnosticsPath";
private const string LOGGING_DIAGNOSTICS_URL_KEY = LOGGING_KEY + "diagnostics_endpoint_url";
private const string CLIENT_AUTH_KEY = APPLICATION_KEY + "ClientAuth:";
private const string CORS_WHITELIST_KEY = CLIENT_AUTH_KEY + "cors_whitelist";
@ -211,7 +213,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
NodesStorage = GetStorageConfig(configData, NODES_STORAGE_KEY),
SimulationsStorage = GetStorageConfig(configData, SIMULATIONS_STORAGE_KEY),
DevicesStorage = GetStorageConfig(configData, DEVICES_STORAGE_KEY),
PartitionsStorage = GetStorageConfig(configData, PARTITIONS_STORAGE_KEY)
PartitionsStorage = GetStorageConfig(configData, PARTITIONS_STORAGE_KEY),
DiagnosticsEndpointUrl = configData.GetString(LOGGING_DIAGNOSTICS_URL_KEY) + "/diagnosticsevents"
};
}

Просмотреть файл

@ -124,6 +124,9 @@ ExtraDiagnostics = false
# be created or is not writable.
ExtraDiagnosticsPath = "/tmp/sim/"
# Path to the diagnostics back end where all telemetry data is to be directed
diagnostics_endpoint_url = "${?PCS_DIAGNOSTICS_WEBSERVICE_URL}"
[DeviceSimulationService:RateLimits]
# S3: 5000/min/unit (= 83.3/sec/unit)
@ -252,4 +255,4 @@ clock_skew_seconds = 300
IncludeScopes = true
LogLevel:Default = "Warning"
LogLevel:System = "Warning"
LogLevel:Microsoft = "Warning"
LogLevel:Microsoft = "Warning"

Просмотреть файл

@ -109,8 +109,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
}
catch (Exception e)
{
errors.Add("Unable to fetch simulation status");
this.log.Error("Unable to fetch simulation status", e);
var msg = "Unable to fetch simulation status";
errors.Add(msg);
this.log.Error(msg, e);
}
return simulationRunning;