Application Insights does not track unhandled browser exceptions in Angular app - javascript

I'm experiencing an issue with the Microsoft Application Insights SDK for JavaScript that was closed/fixed awhile ago: https://github.com/Microsoft/ApplicationInsights-JS/issues/282
I created a brand new Angular app using the Angular CLI. Then I made these changes, following this article.
Added a monitoring service:
import {Injectable} from '#angular/core';
import {AppInsights} from 'applicationinsights-js';
#Injectable()
export class MonitoringService {
private config: Microsoft.ApplicationInsights.IConfig = {
instrumentationKey: 'KEY_GOES_HERE',
enableDebug: true,
verboseLogging: true
};
constructor() {
if (!AppInsights.config) {
AppInsights.downloadAndSetup(this.config);
}
}
logPageView(name?: string, url?: string, properties?: any, measurements?: any, duration?: number) {
AppInsights.trackPageView(name, url, properties, measurements, duration);
}
logEvent(name: string, properties?: any, measurements?: any) {
AppInsights.trackEvent(name, properties, measurements);
}
trackException(exception: Error) {
AppInsights.trackException(exception);
}
}
Added it to my app.component.ts:
import {Component, OnInit} from '#angular/core';
import {MonitoringService} from './monitoring.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [MonitoringService]
})
export class AppComponent implements OnInit {
title = 'app works!';
constructor(private monitoringService: MonitoringService) { }
ngOnInit() {
this.monitoringService.logPageView();
}
throwAnException() {
this.monitoringService.trackException(new Error('manually track exception'));
throw 'this should appear in app insights'; // but it doesn't
}
}
Made a simple button for throwing the exception in my app.component.html:
<h1>
{{title}}
</h1>
<div (click)="throwAnException()">Click to throw an exception</div>
Logging a page view works, as does tracking the exception by explicitly calling trackException. From reading the documentation and various articles, I was under the impression that uncaught exceptions would always automatically get sent to Application Insights. However, I am not seeing any of those show up in the portal.
What could I be missing here?
Using these versions:
applicationinsights-js: 1.0.11
#types/applicationinsights-js: 1.0.4

I've struggles with the same thing and here is the things you need to know to hack it through:
What is happenning?
Angular catches all the exceptions (swallows them!) and just logs them inside console. I have not seen this behavior being explicitly told in any documentation, but I've tested this in code, so trust me. On the other hand only uncaught exceptions are autocollected! (see here). For collecting caught exceptions ( as is mostly the case when using angular framework) you have to call trackException() explicitly in your code.
How to solve it :
We will implement a service (called MonitoringService in code below) to communicate with azure application insights. Then we will tell angular to use this service to log exceptions in azure ai, instead of logging just into browser console, by extending ErrorHandler class.
1) implement MonitoringService:
We'll be using a service named MonitoringService to communicate with azure application insights. Implement that service like this:
import { Injectable } from "#angular/core";
import { ApplicationInsights } from "#microsoft/applicationinsights-web";
import { environment } from "#env/environment";
#Injectable({
providedIn: "root",
})
export class MonitoringService {
private appInsights: ApplicationInsights;
constructor() {}
startMonitoring(): void {
this.appInsights = new ApplicationInsights({
config: {
instrumentationKey: environment.appInsights.instrumentationKey,
},
});
this.appInsights.loadAppInsights();
this.appInsights.trackPageView();
}
logException(exception: Error, severityLevel?: number) {
this.appInsights.trackException({
exception: exception,
severityLevel: severityLevel,
});
}
}
startMonitoring() should be called on app start up.
2) start monitoring on app start up:
Angular projects mostly have a app.component.ts file which belongs to the root module and is bootstrapped/initialized as the first component. By the term "on app start up", I actually mean the time this component is being initialized.
We'll create an instance of MonitoringService and have it start its job:
import { Component } from '#angular/core';
import { MonitoringService } from 'services/monitoring.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
constructor(
private monitoringService: MonitoringService
) {
this.monitoringService.startMonitoring();
}
}
3) Log errors into application insights, before they are swallowed by framework:
Extend ErrorHandler class in your project. This class is actually a hook for centralized exception handling in angular spa. Use this hook, to log exceptions before they are swallowed by framework:
import { Injectable, ErrorHandler } from '#angular/core';
import { MonitoringService } from './monitoring.service';
#Injectable({
providedIn: 'root'
})
export class GlobalErrorHandlerService implements ErrorHandler {
constructor(private monitoringService: MonitoringService) { }
handleError(error: any): void {
console.error(error);
this.monitoringService.logException(error);
}
}
4) Register the ErrorHandler with Angular:
In the AppModule make sure to register this Handler with Angular:
#NgModule({
providers: [{provide: ErrorHandler, useClass: GlobalErrorHandlerService}]
})
class AppModule {}

I don't think AppInsights has any knowledge of Angular and the other way around, Angular doesn't know about app insights so you'll probably have to add this in by yourself using a custom ErrorHandler. Have a look at the ErrorHandler official documentation. If you put your this.monitoringService.trackException call in the handleError there it should work fine.

Related

Angular: How to override the shared module service from different module

I have multiple modules say SchoolModule, UniversityModule, SharedModule
SharedModule has BaseService to which both SchoolModule and UniversityModule providers are extending
Now when I load my SchoolModule, I want BaseService should get the implementation of schoolService, and the same goes for UniversityModule
Structure
app
-- SharedModule
-- base.service
-- secret.service uses base.service
-- shared.component uses secret.service
-- SchoolModule
-- school.component uses shared.component
-- school.service
-- UniversityModule
-- university.component uses shared.component
-- university.service
StackBlitz
So how I can achieve this with Dependency Injection?
You have to declare your base service class as an abstract class with an abstract method getName()
export abstract class BaseService{
abstract getName(): string { return 'Base Service' }
}
export class SchoolService extends BaseService{
getName(): string { return 'School Service' }
}
Unfortunately, Angular2 can not inject a class from another module without get the import between modules, and if you need to lazy load it, things do not work.
There is a project that load dynamically a component from another module.
It is difficult to import this in to your project, but saves you to not code twice.
This project can be found here.
The problem is that SecretService provided is the one build with build in UniversityModule last imported module in App Module so if you want to have SecretService update with correctly BaseService you have to provide both BaseService and SecretService in university and school component something like this in school.component.ts:
import { SchoolService } from './school.service'
import { BaseService } from '../shared/base.service'
import { SecretService } from '../shared/secret.service'
#Component({
selector: 'app-school',
template: `SchoolName :: <app-shared></app-shared>`,
providers: [
{ provide: BaseService, useClass: SchoolService },
SecretService]
})
export class SchoolComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
and in university.component.ts
import {  UniversityService } from './university.service'
import { BaseService } from '../shared/base.service'
import { SecretService } from '../shared/secret.service'
#Component({
selector: 'app-university',
template: `UniversityName :: <app-shared></app-shared>`,
providers: [{ provide: BaseService, useClass: UniversityService },
SecretService]
})
export class UniversityComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
But why you wanna wrap base service in SecretService instead directly use BaseService in shared component ?
Using BaseService like this in shared.component.ts
import { Component, OnInit } from '#angular/core';
import { BaseService } from './base.service'
#Component({
selector: 'app-shared',
template: `{{name}}`
})
export class SharedComponent implements OnInit {
name: string
constructor(private ss: BaseService) {
this.name = ss.getName()
}
ngOnInit() {
}
}
Using BaseService you will solve this injection problem without any workaround on SecretService

Angular 6:services with providedIn: 'root' returns empty object in component

My service located in src/core/services/api.service.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ApiService {
constructor() { }
test() {
console.log('hello from services');
}
}
I am trying to invoke this service from a component which in another module.
home.component.ts
import { Component, OnInit } from '#angular/core';
import {ApiService} from './core/services/api.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private api: ApiService) { }
ngOnInit() {
console.log(this.api);
}
}
But I am getting an empty object like this ApiService {}
So it is returning an object and that means that the service is initialized correctly, instead of console.log(this.api); try console.log(this.api.test()), you should see hello from services in the console.
Same thing happened for me in Angular 8.
I added public in front of the function then it started working:
public test()
I had some unused imports in the service.
Removing those imports and restarting ng serve worked for me.

500 when calling setTitle using angular 2 titleService and server side rendering

I have a problem with server side rendering using angular 2 and titleService.
My code looks like this
import { Component } from '#angular/core';
import { BrowserModule, Title } from '#angular/platform-browser';
import { Router } from '#angular/router';
#Component({
selector: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [Title]
})
export class AppComponent {
constructor(private titleService: Title) {
titleService.setTitle("Setting the title...");
}
}
This works fine using client side rendering but when reloading the page I get this error:
Exception: Call to Node module failed with error: TypeError: Cannot create property 'title' on string ''
Any ideas why this occurs?
With angular universal there should be no need to provide any external service as this is built in. (as echonax stated in the comments.)
Working example with this angular-universal fork. I guess it should be the same for your version of angular-universal.
app.component.ts
import { Component, OnInit } from '#angular/core';
import { Router, NavigationEnd } from '#angular/router';
import { Meta, Title } from '#angular/platform-browser';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
constructor(private _router: Router, private _meta: Meta, private _title: Title) { }
ngOnInit() {
this._router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
switch (event.urlAfterRedirects) {
case '/':
this._title.setTitle('title goes here');
this._meta.updateTag({ name: 'description', content: 'same goes for meta content' });
break;
case '/another-route':
this._title.setTitle('Another title');
this._meta.updateTag({ name: 'description', content: 'You get the idea' });
break;
}
}
});
}
}
NavigationEnd takes care of setting a new title each time I navigate to a new route.
Hope it helps.
I guess this might be expected since the titleService interacts with element only present in the browser. When reading the "Universal Gotchas" it clearly status that you need to check if you are on the client or in the browser when doing this. I expected the titleService to handle such things though. However checking if client solved the problem.
Se: https://github.com/angular/universal

Angular 2 Http Get Not Working

I tried everything and I cannot get an http request to go out to my node server on heroku. I can hit the route manually so its not the server. I will paste my service and my page.
**Class is subscription.service.ts
import {Http, Response} from '#angular/http'
import {Injectable} from '#angular/core'
import 'rxjs/add/operator/map';
#Injectable()
export class SubscriptionService {
http:Http;
constructor(http:Http){
this.http = http;
}
getEntries() {
return this.http.get('my url here *****').map(res => res.json());
}
}
**Class is dashboard.component.ts
import {Component, ViewEncapsulation} from '#angular/core';
import {SubscriptionService} from '../../_services/subscription.service';
import 'rxjs/Rx';
#Component({
selector: 'dashboard',
providers: [SubscriptionService],
encapsulation: ViewEncapsulation.None,
styles: [require('./dashboard.scss')],
template: require('./dashboard.html')
})
export class Dashboard {
getData: string;
constructor(private subscriptionService: SubscriptionService) {
}
ngOnInit() {
console.log("test!!");
this.subscriptionService.getEntries()
.subscribe(data => this.getData = JSON.stringify(data),
error => console.log("Error!"),
() => console.log("finished!")
);
}
}
My ngOnInit() is being called, I see the console print, but no request shows up in logs on heroku. Also no errors show up in console.
Make sure you have imported the HttpModule in root.
I don't see anything else which can cause this. For make sure http is working you can put a break point in SubscriptionService on getEntries method and follow where it leads you.
Update:- as pointed out by #peeskillet there is nothing wrong with your dependency. try to debug and update your question with more information.

Angular 2 No Provider for LoadingService

I've already read similar questions regarding this on StackOverflow, but they don't seem to be working for my situation. I'm on the most recent version of Angular.
Loader Component
import {Component, ElementRef, OnInit, OnDestroy} from '#angular/core';
import {CORE_DIRECTIVES} from "#angular/common";
import {LoadingService} from "../../services/global/loadingService";
#Component({
selector: 'loader',
directives: [CORE_DIRECTIVES],
providers: [LoadingService],
template: `
<div class ="blue"></div>
`,
})
export class LoadingComponent {
public active: boolean;
public constructor(spinner: LoadingService) {
spinner.status.subscribe((status: boolean) => {
this.active = status;
});
}
}
Loader Service
import {Injectable} from '#angular/core';
import {Subject} from 'rxjs/Subject';
import 'rxjs/add/operator/share';
#Injectable()
export class LoadingService {
public status: Subject<any> = new Subject();
private _active: boolean = false;
public get active(): boolean {
return this._active;
}
public set active(v: boolean) {
this._active = v;
this.status.next(v);
}
public start(): void {
this.active = true;
}
public stop(): void {
this.active = false;
}
}
My error is: Error: Uncaught (in promise): No provider for LoadingService!
Any help would be greatly appreciated. Thank you!
There is nothing wrong with the code you posted here. I put it in my project and it loads without any issues. Your issue is most likely your SystemJS configuration or some other issue that prevents the app from loading correctly, and therefore can't resolve how to inject your LoadingService. Use Chrome's dev tools to look at the console when the app loads and see if it shows you any other issues. Alternatively, post your full non-working sample somewhere we can actually see all the code (like Plunkr, or JSFiddle).
I faced a similar issue and solved it by importing the service in the providers specified in app.module.ts

Categories