Using Serilog with .Net core and App Insights - .net-core

I have used App insights directly for application logging before and I have seen that .Net core platform also creates trace events that goes to App insights.
In a new .Net core API application, I'd like to use Serilog for application logging and App Insight for storing and visualizing the log events. I'd like to know:
How to continue to get the .Net core .created trace events to App insights?
How can I pass correlation Id from my application to .Net core created trace events?
Will end to end transaction feature in App insight portal show all the events together? It is important for me to know and keep an eye on the latency of SQL calls.

Simply using Serilog.Sinks.ApplicationInsights is not enough, as it will not correlate Serilog events with the rest of your telemetry on Application Insights.
To correlate the events, so they are shown as one "End-to-End transaction" - you have to do the following things:
Create a Serilog enricher that would record the current Activity id as a ScalarValue in a LogEventProperty - see OperationIdEnricher
[Optional] Create an extension for this enricher - see LoggingExtensions
Register the enricher / add it to the pipeline via code or config - see logging.json
Create a custom TelemetryConverter (subclass from TraceTelemetryConverter or EventTelemetryConverter) for ApplicationInsights that would set telemetry.Context.Operation.Id from value set in 1) - see OperationTelemetryConverter
Check out my blog post "Serilog with ApplicationInsights" that explains the points above with more details, and links
Also, be sure to take a look at Telemetry correlation in Application Insights on MSDN

If you are using ILogger in .Net core for logging purposes, those message can be directed to Application Insights with the following modification of startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
/*...existing code..*/
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Warning);
}
If you employ your own correlation ID, you can modify Application Insights correlation IDs accordingly in Context.Operation field of telemetry item with your own Telemetry Initializer or pass those values in the respective headers (Request-ID (global ID) and Correlation-Context (name-value pairs)) in the requests to this app - AI will pick up correlation IDs from those.
The end to end transaction is supposed to be displayed together (Requests/Dependencies and Exceptions) on a timeline in the details view of Application Insights telemetry. With you own correlation IDs it should work as well if they are in there from the very beginning of transaction (e.g. in the first component) - otherwise injecting them in the middle will break the chain.

Related

Codeless Application Insights Custom Counters

I'm running the codeless version of Application Insights in a Windows Server 2016 Azure VM. With the SDK I know it is possible to, for example, add custom telemetry so that I can update the cloudRoleName value that appears in my metrics.
My problem is that for the Performance Counters that are pushed by Application Insights it only provides a value like w3wp#1 for process related data, but I really want to be able to relate this process to an application pool (ideally to a cloudRoleName)
Can I add any configuration to the App Insights agent that will allow me to add custom telemetry or will I have to add the SDK to each of the Dotnet Applications that are running on this VM to achieve this?
If I understand you correctly, you want to provide a custom value for cloudRoleName, right?
If that's the case, the only way is to use code(no way for codeless, see this issue.) by using ITelemetryInitializer, here is an example:
public class CloudRoleNameTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
// set custom role name here
telemetry.Context.Cloud.RoleName = "Custom RoleName";
}
}
For more details, you can refer to this article.

Not able to connect to Azure Key Vault when using Service Identity

I am trying to retrieve secrets from Azure Key Vault using Service Identity in an ASPNet 4.6.2 web application. I am using the code as outlined in this article. Locally, things are working fine, though this is because it is using my identity. When I deploy the application to Azure I get an exception when keyVaultClient.GetSecretAsync(keyUrl) is called.
As best as I can tell everything is configured correctly. I created a User assigned identity so it could be reused and made sure that identity had get access to secrets and keys in the KeyVault policy.
The exception is an AzureServiceTokenProviderException. It is verbose and outlines how it tried four methods to authenticate. The information I'm concerned about is when it tries to use Managed Service Identity:
Tried to get token using Managed Service Identity. Access token could
not be acquired. MSI ResponseCode: BadRequest, Response:
I checked application insights and saw that it tried to make the following connection with a 400 result error:
http://127.0.0.1:41340/MSI/token/?resource=https://vault.azure.net&api-version=2017-09-01
There are two things interesting about this:
Why is it trying to connect to a localhost address? This seems wrong.
Could this be getting a 400 back because the resource parameter isn't escaped?
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
At this point I don't know what to do. The examples online all make it seem like magic, but if I'm right about the source of the problem then there's some obscure automated setting that needs fixing.
For completeness here is all of my relevant code:
public class ServiceIdentityKeyVaultUtil : IDisposable
{
private readonly AzureServiceTokenProvider azureServiceTokenProvider;
private readonly Uri baseSecretsUri;
private readonly KeyVaultClient keyVaultClient;
public ServiceIdentityKeyVaultUtil(string baseKeyVaultUrl)
{
baseSecretsUri = new Uri(new Uri(baseKeyVaultUrl, UriKind.Absolute), "secrets/");
azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
}
public async Task<string> GetSecretAsync(string key, CancellationToken cancellationToken = new CancellationToken())
{
var keyUrl = new Uri(baseSecretsUri, key).ToString();
try
{
var secret = await keyVaultClient.GetSecretAsync(keyUrl, cancellationToken);
return secret.Value;
}
catch (Exception ex)
{
/** rethrows error with extra details */
}
}
/** IDisposable support */
}
UPDATE #2 (I erased update #1)
I created a completely new app or a new service instance and was able to recreate the error. However, in all instances I was using a User Assigned Identity. If I remove that and use a System Assigned Identity then it works just fine.
I don't know why these would be any different. Anybody have an insight as I would prefer the user assigned one.
One of the key differences of a user assigned identity is that you can assign it to multiple services. It exists as a separate asset in azure whereas a system identity is bound to the life cycle of the service to which it is paired.
From the docs:
A system-assigned managed identity is enabled directly on an Azure service instance. When the identity is enabled, Azure creates an identity for the instance in the Azure AD tenant that's trusted by the subscription of the instance. After the identity is created, the credentials are provisioned onto the instance. The lifecycle of a system-assigned identity is directly tied to the Azure service instance that it's enabled on. If the instance is deleted, Azure automatically cleans up the credentials and the identity in Azure AD.
A user-assigned managed identity is created as a standalone Azure resource. Through a create process, Azure creates an identity in the Azure AD tenant that's trusted by the subscription in use. After the identity is created, the identity can be assigned to one or more Azure service instances. The lifecycle of a user-assigned identity is managed separately from the lifecycle of the Azure service instances to which it's assigned.
User assigned identities are still in preview for App Services. See the documentation here. It may still be in private preview (i.e. Microsoft has to explicitly enable it on your subscription), it may not be available in the region you have selected, or it could be a defect.
To use a user-assigned identity, the HTTP call to get a token must include the identity's id.
Otherwise it will attempt to use a system-assigned identity.
Why is it trying to connect to a localhost address? This seems wrong.
Because the MSI endpoint is local to App Service, only accessible from within the instance.
Could this be getting a 400 back because the resource parameter isn't escaped?
Yes, but I don't think that was the reason here.
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
These are added by App Service invisibly, not added to app settings.
As for how to use the user-assigned identity,
I couldn't see a way to do that with the AppAuthentication library.
You could make the HTTP call manually in Azure: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http.
Then you gotta take care of caching yourself though!
Managed identity endpoints can't handle a lot of queries at one time :)

How to properly configure Spring Datasource for an Elastic Beanstalk app?

I'm running into an issue integrating Spring Security with my Elastic Beanstalk app backed by a MySql database. If I deploy my app I'm able to login in correctly for some time but eventually I'll start to receive login errors without an exception being thrown so I'm unable to get any useful information about the issue. I've downloaded the logs as well and can't see anything of value. I can see where the logs show accessing the public page, attempting to access the private section, returning the login page, and then the loginError page; however, nothing about any issue.
Even though I'm unable to login through a browser I am able to login if I run the app from an IDE as well as view the db in MySQL Workbench. This suggests to me the problem is due to some persistent state on the server.
I've had a similar problem before with another Beanstalk app using Spring Security and was able to resolve it by setting application properties as follows:
spring.datasource.test-on-borrow=true
spring.datasource.validation-query=SELECT 1
I'm using a more recent version of Spring than that app and the properties have been changed to specific datasources so I tried adding the following properties:
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.validation-query=SELECT 1
When that didn't work I added another based on an answer to a similar question here; now the properties are:
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.test-while-idle=true
spring.datasource.tomcat.validation-query=SELECT 1
That seemed to work (possibly due to less login activity) but eventually resulted in the same behavior .
I've looked into the various properties available but before I spend a lot of time randomly setting and/or overriding default settings I wanted to see if there's a reliable way to deal with this.
How can I configure my datasource to avoid login errors after long periods of time?
This isn't a problem of specific configuration values but with where those configurations reside. The default location for the application.properties (/resources; Intellij) is fine for deploying as a jar with an embedded Tomcat server but not as a war with a provided server. The file isn't found/used so no changes to the file affect the one given by AWS.
There are a number of ways to handle this; I chose to add an RDS configuration bean in my SpringBootServletInitializer:
#Bean
public RdsInstanceConfigurer instanceConfigurer() {
return () -> {
TomcatJdbcDataSourceFactory dataSourceFactory =
new TomcatJdbcDataSourceFactory();
// Abondoned connections...
dataSourceFactory.setRemoveAbandonedTimeout(60);
dataSourceFactory.setRemoveAbandoned(true);
dataSourceFactory.setLogAbandoned(true);
// Tests
dataSourceFactory.setTestOnBorrow(true);
dataSourceFactory.setTestOnReturn(false);
dataSourceFactory.setTestWhileIdle(false);
// Validations
dataSourceFactory.setValidationInterval(30000);
dataSourceFactory.setTimeBetweenEvictionRunsMillis(30000);
dataSourceFactory.setValidationQuery("SELECT 1");
return dataSourceFactory;
};
}
Below are the settings that worked for me.
From Connection to Db dies after >4<24 in spring-boot jpa hibernate
dataSourceFactory.setMaxActive(10);
dataSourceFactory.setInitialSize(10);
dataSourceFactory.setMaxIdle(10);
dataSourceFactory.setMinIdle(1);
dataSourceFactory.setTestWhileIdle(true);
dataSourceFactory.setTestOnBorrow(true);
dataSourceFactory.setValidationQuery("SELECT 1 FROM DUAL");
dataSourceFactory.setValidationInterval(10000);
dataSourceFactory.setTimeBetweenEvictionRunsMillis(20000);
dataSourceFactory.setMinEvictableIdleTimeMillis(60000);

Spring Integration: JDBC single query to web service

I would like to know the way for resolving this integration scenario:
Execute different queries to select X elements from a database. I am
looking for an inbound adapter without pooling because it is just
necessary to execute the query once. Although, results of the queries
will be generate only one output.
Work with this data to build a SOAP request (generic web service)
Send this SOAP request to a web service and wait for an asynchronous response.
But also, it is necessary to deploy all this scenario in a WAR file on Tomcat server. I am deploying the application from a spring MVC + spring integration skeleton but I will not have any controller. Is it possible to execute the application when context was loaded on Tomcat?
I am working with the next technologies:
Spring integration
Spring MVC for a WAR deployment
Scheduling (Quartz or #Scheduled)
Spring WS
Regards
Since you say that you'd prefer to select on the application start up and only once, you can use:
<int-event:inbound-channel-adapter channel="jdbcChannel"
event-types="org.springframework.context.event.ContextRefreshedEvent"
payload-expression="''"/>
and <int-jdbc:outbound-gateway query="SELECT * FROM ..."/>
And so on to the WebService.
UPDATE
Since you say that you are around Anotation configuration, consider to use Spring Integration Java DSL.
To configure <int-event:inbound-channel-adapter> from #Configuration you should do this:
#Bean
#SuppressWarnings("unchecked")
public MessageProducer ApplicationEventListeningMessageProducer() {
ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer();
producer.setEventTypes(ContextRefreshedEvent.class);
producer.setPayloadExpression("''");
producer.setOutputChannel(jdbcChannel());
return producer;
}
ContextRefreshedEvent info you can get from its JavaDocs or from Spring Framework Manual.

asp.net mvc integration test

Hi Im doing TDD for an asp.net mvc project, I need to be able to do end to end testing for sending a request to the controller action all the way to the repository. I have tried using the code here but unfortunately I can't get this to run and I'm running out of time, does anyone know any other way to fake an http request and populate request post parameters in a test scenario?
My controller action is as follows:
[HttpPost]
public ActionResult CreateUser(User user)
{
}
So I need to basically do an http request to populate this User object and hopefully save it to a test repository.
As you posted the link I'll take an extract from Steve Sanderson's blog:
Integration tests test your entire software stack working together. These tests don’t mock or fake anything (they use the real database, and real network connections) and are good at spotting if your unit-tested components aren’t working together as you expected. In general, it’s best to put most of your effort into building a solid suite of unit tests, and then adding a few integration tests for each major feature so you can detect any catastrophic incompatibilities or configuration errors before your customers do.
You shouldn't be faking HTTP requests at this stage as an integration test inherantly tests every component together.
Try some type of browser automation framework:
http://blog.stevensanderson.com/2010/03/30/using-htmlunit-on-net-for-headless-browser-automation/
http://www.codeproject.com/KB/cs/mshtml_automation.aspx
If you want to do full integration testing, then test your application from user prospective. Create test cases like:
Log in as admin
Go to Users page
Add User with name "User1"
Check that user with name "User1" listed in the Users grid.
And automate such tests using Selenium or Watin. See example here
You may also want to take a look at the Verde framework. Semantically the tests look similar to Steve Sanderson's MvcIntegrationTestFramework with the key difference being that Verde executes tests in the context of your actual IIS AppDomain (via a browser-based test runner) rather than a programmatically created one. This provides a couple of advantages: First it is a more realistic emulation of your actual application's configuration, network topology, security settings, etc. Secondly you can automate running of the tests as a post-deployment step or could even run the tests automatically as part of application monitoring in production. Here is an example Verde test taken from the MvcMusicStore sample that is included in the source code on GitHub:
[IntegrationTest]
public void Index_Load_ExpectedHtml()
{
// Get a product to load the details page for.
var album = storeDB.Albums
.Take(1)
.First();
using (var scope = new MvcExecutorScope("Store/Details/" + album.AlbumId))
{
Assert.AreEqual(200, scope.HttpContext.Response.StatusCode);
Assert.IsTrue(scope.Controller is StoreController);
Assert.AreEqual("Details", scope.Action);
var model = scope.Controller.ViewData.Model as Album;
Assert.IsNotNull(model);
Assert.AreEqual(album.AlbumId, model.AlbumId);
Assert.IsFalse(String.IsNullOrEmpty(scope.ResponseText));
// Load the ResponseText into an HtmlDocument
var html = new HtmlDocument();
html.LoadHtml(scope.ResponseText);
// Use ScrappySharp CSS selector to make assertions about the rendered HTML
Assert.AreEqual(album.Title, html.DocumentNode.CssSelect("#main h2").First().InnerText);
}
}
There is a NuGet package which makes it very easy to add to your MVC project.
http://dvonlehman.github.com/Verde/
https://nuget.org/packages/Verde/0.5.1

Resources