feat(logging): Added Elastic APM RUM logging. (TV-316)
Squashed commit of the following: commit 3c4abbe69605caff2a39efafd90550d93e9e1447 Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 15:56:41 2021 +0200 Updated npm scripts and built/serve shell commit 00525a666fb6b3146ea5f85c7c3ad741378401de Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 13:59:12 2021 +0200 Updated nginx-conf commit a9945c14cc93eebf8812c075fe8ca67e39ab8ae8 Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 12:59:49 2021 +0200 Added elastics serverUrl to environment files and fixed nginx-conf commit 38872cea957ce54c5cb496890e4be88fb019be58 Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 12:49:41 2021 +0200 Added Elastic APM with error handling commit d3db5e8703e3b0a1d0d0b24230dc52a64bee252c Merge: a3bc70e9d139f750Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 12:22:16 2021 +0200 Merge branch 'develop' into feature/TV-316-RUM commit a3bc70e9420dc5d309325cfcf1221c6760d18c38 Merge: 3f98a66bc2a02dbaAuthor: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Mon Oct 4 09:07:11 2021 +0200 Merge branch 'develop' into feature/TV-316-RUM commit 3f98a66bfda3af315c5417e2d2902ab80877b98b Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Fri Oct 1 16:05:43 2021 +0200 Added @elastic/apm-rum-angular to log errors and api-requests
This commit is contained in:
@@ -1,20 +1,23 @@
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import localeSe from '@angular/common/locales/sv';
|
||||
import { ErrorHandler, LOCALE_ID, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
||||
import { AuthInterceptor } from '@msfa-interceptors/auth.interceptor';
|
||||
import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler.module';
|
||||
import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ToastListModule } from './components/toast-list/toast-list.module';
|
||||
import { LoggingModule } from './logging.module';
|
||||
import { AvropModule } from './pages/avrop/avrop.module';
|
||||
import localeSe from '@angular/common/locales/sv';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
registerLocaleData(localeSe);
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
|
||||
imports: [LoggingModule, BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
|
||||
providers: [
|
||||
ApmErrorHandler,
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: CustomErrorHandler,
|
||||
|
||||
36
apps/mina-sidor-fa/src/app/logging.module.ts
Normal file
36
apps/mina-sidor-fa/src/app/logging.module.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ApmModule, ApmService } from '@elastic/apm-rum-angular';
|
||||
import { Feature } from '@msfa-enums/feature.enum';
|
||||
import { environment } from '@msfa-environment';
|
||||
|
||||
@NgModule({
|
||||
imports: [ApmModule],
|
||||
exports: [ApmModule],
|
||||
})
|
||||
export class LoggingModule {
|
||||
private _elasticConfig = environment.elastic;
|
||||
private _activeFeatures = environment.activeFeatures;
|
||||
|
||||
constructor(private apmService: ApmService) {
|
||||
if (this._elasticConfig && this._activeFeatures.includes(Feature.LOGGING)) {
|
||||
const { serviceName, serverUrl } = this._elasticConfig;
|
||||
this.apmService.init({
|
||||
serviceName,
|
||||
serverUrl,
|
||||
environment: this.currentEnvironment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get currentEnvironment(): string {
|
||||
const defaultEnvironment = environment.elastic.environment;
|
||||
const testUrlRegEx = new RegExp(/(?:mina-sidor-fa-)(\w{1,})(?:\.tocp)/g);
|
||||
const testEnvironment = testUrlRegEx.exec(window.location.origin);
|
||||
|
||||
if (testEnvironment?.length) {
|
||||
return testEnvironment[1].toUpperCase();
|
||||
}
|
||||
|
||||
return defaultEnvironment;
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,18 @@ import { Observable } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PeriodiskRedovisningService {
|
||||
private _apiBaseUrl = `${environment.api.url}`;
|
||||
|
||||
public getActivities$(): Observable<Activity[]> { // endpoint ska uppdateras
|
||||
public getActivities$(): Observable<Activity[]> {
|
||||
// endpoint ska uppdateras
|
||||
return this.httpClient.get<{ data: ActivityResponse[] }>(`${this._apiBaseUrl}/activities`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(aktivitet => mapResponseToActivity(aktivitet)))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AuthenticationService } from '@msfa-services/api/authentication.service
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class LogoutComponent implements OnInit {
|
||||
loginUrl = environment.loginUrl;
|
||||
loginUrl = environment.ciam.loginUrl;
|
||||
|
||||
constructor(private authenticationService: AuthenticationService) {}
|
||||
|
||||
|
||||
@@ -10,4 +10,5 @@ export enum Feature {
|
||||
ACCESSIBILITY_REPORT,
|
||||
REPORTING,
|
||||
SENSITIVE_INFORMATION,
|
||||
LOGGING,
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ export class AuthGuard implements CanActivate {
|
||||
|
||||
void this.authenticationService.removeLocalStorageData();
|
||||
|
||||
if (environment.environment === 'local') {
|
||||
void this.router.navigateByUrl(environment.loginUrl);
|
||||
if (environment.ciam.clientId) {
|
||||
document.location.href = `${environment.ciam.loginUrl}&client_id=${environment.ciam.clientId}&redirect_uri=${window.location.origin}`;
|
||||
} else {
|
||||
document.location.href = `${environment.loginUrl}&client_id=${environment.clientId}&redirect_uri=${window.location.origin}`;
|
||||
void this.router.navigateByUrl(environment.ciam.loginUrl);
|
||||
}
|
||||
return of(false);
|
||||
})
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { CustomError, errorToCustomError } from '@msfa-models/error/custom-error';
|
||||
import { ErrorService } from '@msfa-services/error.service';
|
||||
|
||||
@Injectable()
|
||||
export class CustomErrorHandler implements ErrorHandler {
|
||||
constructor(private errorService: ErrorService) {}
|
||||
|
||||
handleError(error: Error & { ngDebugContext: unknown }): void {
|
||||
const customError: CustomError = errorToCustomError(error);
|
||||
console.error(error);
|
||||
this.errorService.add(customError);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ErrorHandler, Injectable } from '@angular/core';
|
||||
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
||||
import { Feature } from '@msfa-enums/feature.enum';
|
||||
import { environment } from '@msfa-environment';
|
||||
import { CustomError, errorToCustomError } from '@msfa-models/error/custom-error';
|
||||
import { ErrorService } from '@msfa-services/error.service';
|
||||
|
||||
@Injectable()
|
||||
export class CustomErrorHandler implements ErrorHandler {
|
||||
private _elasticConfig = environment.elastic;
|
||||
private _activeFeatures = environment.activeFeatures;
|
||||
constructor(private errorService: ErrorService, public apmErrorHandler: ApmErrorHandler) {}
|
||||
|
||||
handleError(error: Error & { ngDebugContext: unknown }): void {
|
||||
const customError: CustomError = errorToCustomError(error);
|
||||
this.errorService.add(customError);
|
||||
|
||||
if (this._elasticConfig && this._activeFeatures.includes(Feature.LOGGING)) {
|
||||
this.apmErrorHandler.handleError(customError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
import { Feature } from '@msfa-enums/feature.enum';
|
||||
|
||||
export interface Environment {
|
||||
environment: 'api' | 'local' | 'acc' | 'prod';
|
||||
version?: string;
|
||||
clientId: string;
|
||||
loginUrl: string;
|
||||
logoutUrl: string;
|
||||
production: boolean;
|
||||
api: {
|
||||
url: string;
|
||||
headers: { [key: string]: string };
|
||||
skipHeadersOn: string[];
|
||||
};
|
||||
ciam: {
|
||||
clientId?: string;
|
||||
loginUrl: string;
|
||||
logoutUrl: string;
|
||||
};
|
||||
activeFeatures: Feature[];
|
||||
elastic?: {
|
||||
serviceName: string;
|
||||
serverUrl: string;
|
||||
environment?: 'DEV' | 'SYS' | 'TEST' | 'ACC' | 'PROD';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '@msfa-environment';
|
||||
import { ActivityResponse } from '@msfa-models/api/activity-response.model';
|
||||
import { Activity, mapResponseToActivity } from '@msfa-models/activity.model';
|
||||
import { ActivityResponse } from '@msfa-models/api/activity-response.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
|
||||
export class ActivityApiService {
|
||||
|
||||
private _apiBaseUrl = `${environment.api.url}`;
|
||||
|
||||
public getActivities$(): Observable<Activity[]> { // endpoint ska uppdateras
|
||||
public getActivities$(): Observable<Activity[]> {
|
||||
// endpoint ska uppdateras
|
||||
return this.httpClient.get<{ data: ActivityResponse[] }>(`${this._apiBaseUrl}/aktiviteter`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(aktivitet => mapResponseToActivity(aktivitet)))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ export class AuthenticationService {
|
||||
logout(): void {
|
||||
this.removeLocalStorageData();
|
||||
|
||||
if (environment.environment === 'local') {
|
||||
void this.router.navigateByUrl(environment.logoutUrl);
|
||||
if (environment.ciam.clientId) {
|
||||
document.location.href = environment.ciam.logoutUrl;
|
||||
} else {
|
||||
document.location.href = environment.logoutUrl;
|
||||
void this.router.navigateByUrl(environment.ciam.logoutUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,57 +13,59 @@ import {
|
||||
KandaAvvikelseKoder,
|
||||
mapResponseToAndraKandaOrsaker,
|
||||
mapResponseToOrsaksKoderFranvaro,
|
||||
OrsaksKoderFranvaro
|
||||
OrsaksKoderFranvaro,
|
||||
} from '@msfa-models/orsaks-koder-franvaro.model';
|
||||
import { ErrorService } from '@msfa-services/error.service';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, filter, map, take } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
|
||||
export class AvvikelseApiService {
|
||||
|
||||
private _apiBaseUrl = `${environment.api.url}/report`;
|
||||
|
||||
public getOrsaksKoderFranvaro$(): Observable<OrsaksKoderFranvaro[]> {
|
||||
return this.httpClient.get<{ data: OrsaksKoderAvvikelseResponse[] }>(`${this._apiBaseUrl}/orsakskoderfranvaro`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(orsak => mapResponseToOrsaksKoderFranvaro(orsak)))
|
||||
)
|
||||
return this.httpClient
|
||||
.get<{ data: OrsaksKoderAvvikelseResponse[] }>(`${this._apiBaseUrl}/orsakskoderfranvaro`)
|
||||
.pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(orsak => mapResponseToOrsaksKoderFranvaro(orsak)))
|
||||
);
|
||||
}
|
||||
|
||||
public getOrsaksKoderAvvikelse$(): Observable<OrsaksKoderAvvikelse[]> {
|
||||
return this.httpClient.get<{ data: OrsaksKoderAvvikelseResponse[] }>(`${this._apiBaseUrl}/orsakskoderavvikelse`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(avvikelse => mapResponseToOrsaksKoderAvvikelse(avvikelse)))
|
||||
)
|
||||
return this.httpClient
|
||||
.get<{ data: OrsaksKoderAvvikelseResponse[] }>(`${this._apiBaseUrl}/orsakskoderavvikelse`)
|
||||
.pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(avvikelse => mapResponseToOrsaksKoderAvvikelse(avvikelse)))
|
||||
);
|
||||
}
|
||||
|
||||
public getAndraKandaOrsaker$(): Observable<KandaAvvikelseKoder[]> {
|
||||
return this.httpClient.get<{ data: KandaAvvikelseKoderResponse[] }>(`${this._apiBaseUrl}/kandaavvikelsekoder`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(annanKandOrsak => mapResponseToAndraKandaOrsaker(annanKandOrsak)))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public getFragorForAvvikelser$(): Observable<FragorForAvvikelser[]> {
|
||||
return this.httpClient.get<{ data: FragorForAvvikelserResponse[] }>(`${this._apiBaseUrl}/fragorforavvikelser`).pipe(
|
||||
filter(response => !!response?.data),
|
||||
map(({ data }) => data.map(fraga => mapResponseToFragorForAvvikelser(fraga)))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public createAvvikelse$(avvikelse: Avvikelse, alternative: string): Observable<Avvikelse> {
|
||||
|
||||
return this.httpClient
|
||||
.post<{ data: AvvikelseRequestData }>(`${this._apiBaseUrl}/${this.setEndPoint(alternative)}`, avvikelse).pipe(
|
||||
.post<{ data: AvvikelseRequestData }>(`${this._apiBaseUrl}/${this.setEndPoint(alternative)}`, avvikelse)
|
||||
.pipe(
|
||||
filter(response => !!response?.data),
|
||||
take(1),
|
||||
map(({ data }) => mapAvvikelseRequestDataToAvvikelse(data)),
|
||||
catchError(error => throwError({ message: error as string, type: ErrorType.API }))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private setEndPoint(alternative: string): string {
|
||||
@@ -75,12 +77,12 @@ export class AvvikelseApiService {
|
||||
break;
|
||||
case Alternative.FRANVARO:
|
||||
endpoint = 'franvaro';
|
||||
break
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
constructor(private httpClient: HttpClient, private errorService: ErrorService) { }
|
||||
constructor(private httpClient: HttpClient, private errorService: ErrorService) {}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ export class ErrorService {
|
||||
constructor(private injector: Injector) {
|
||||
// Workaround to fix change-detection when using Error interceptor
|
||||
// See https://stackoverflow.com/a/37793791
|
||||
setTimeout(() => (this.appRef = this.injector.get(ApplicationRef)));
|
||||
setTimeout(() => {
|
||||
this.appRef = this.injector.get(ApplicationRef);
|
||||
});
|
||||
}
|
||||
|
||||
public add(error: CustomError): void {
|
||||
|
||||
@@ -20,4 +20,5 @@ export const ACTIVE_FEATURES_TEST: Feature[] = [
|
||||
Feature.ACCESSIBILITY_REPORT,
|
||||
Feature.REPORTING,
|
||||
Feature.SENSITIVE_INFORMATION,
|
||||
Feature.LOGGING,
|
||||
];
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ACTIVE_FEATURES_PROD } from './active-features';
|
||||
import { CIAM_TEST } from './ciam';
|
||||
|
||||
export const environment: Environment = {
|
||||
environment: 'acc',
|
||||
version: 'acc',
|
||||
production: true,
|
||||
api: {
|
||||
@@ -12,5 +11,10 @@ export const environment: Environment = {
|
||||
skipHeadersOn: ['assets/'],
|
||||
},
|
||||
activeFeatures: [...ACTIVE_FEATURES_PROD],
|
||||
...CIAM_TEST,
|
||||
elastic: {
|
||||
serverUrl: '/logging',
|
||||
serviceName: 'mina-sidor-fa',
|
||||
environment: 'ACC',
|
||||
},
|
||||
ciam: CIAM_TEST,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Feature } from '@msfa-enums/feature.enum';
|
||||
import { Environment } from '@msfa-models/environment.model';
|
||||
import { ACTIVE_FEATURES_TEST } from './active-features';
|
||||
import { CIAM_TEST } from './ciam';
|
||||
import { CIAM_MOCK } from './ciam';
|
||||
|
||||
export const environment: Environment = {
|
||||
environment: 'api',
|
||||
version: 'api',
|
||||
version: 'mock',
|
||||
production: false,
|
||||
api: {
|
||||
url: '/api',
|
||||
headers: {},
|
||||
skipHeadersOn: ['assets/'],
|
||||
skipHeadersOn: [],
|
||||
},
|
||||
activeFeatures: [...ACTIVE_FEATURES_TEST],
|
||||
...CIAM_TEST,
|
||||
activeFeatures: [...ACTIVE_FEATURES_TEST, Feature.MOCK_LOGIN],
|
||||
ciam: CIAM_MOCK,
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import { ACTIVE_FEATURES_PROD } from './active-features';
|
||||
import { CIAM_PROD } from './ciam';
|
||||
|
||||
export const environment: Environment = {
|
||||
environment: 'prod',
|
||||
version: 'prod',
|
||||
production: true,
|
||||
api: {
|
||||
@@ -12,5 +11,10 @@ export const environment: Environment = {
|
||||
skipHeadersOn: ['assets/'],
|
||||
},
|
||||
activeFeatures: [...ACTIVE_FEATURES_PROD],
|
||||
...CIAM_PROD,
|
||||
elastic: {
|
||||
serverUrl: '/logging',
|
||||
serviceName: 'mina-sidor-fa',
|
||||
environment: 'PROD',
|
||||
},
|
||||
ciam: CIAM_PROD,
|
||||
};
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Feature } from '@msfa-enums/feature.enum';
|
||||
import { Environment } from '@msfa-models/environment.model';
|
||||
import { ACTIVE_FEATURES_TEST } from './active-features';
|
||||
import { CIAM_MOCK } from './ciam';
|
||||
import { CIAM_TEST } from './ciam';
|
||||
|
||||
export const environment: Environment = {
|
||||
environment: 'local',
|
||||
version: 'local',
|
||||
version: 'api',
|
||||
production: false,
|
||||
api: {
|
||||
url: '/api',
|
||||
headers: {},
|
||||
skipHeadersOn: [],
|
||||
skipHeadersOn: ['assets/'],
|
||||
},
|
||||
activeFeatures: [...ACTIVE_FEATURES_TEST, Feature.MOCK_LOGIN],
|
||||
...CIAM_MOCK,
|
||||
activeFeatures: [...ACTIVE_FEATURES_TEST],
|
||||
elastic: {
|
||||
serverUrl: '/logging',
|
||||
serviceName: 'mina-sidor-fa',
|
||||
environment: 'DEV',
|
||||
},
|
||||
ciam: CIAM_TEST,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { defineCustomElements } from '@digi/core/loader';
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
import { environment } from './environments/environment.mock';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
|
||||
Reference in New Issue
Block a user