Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: improve logging #1088

Merged
merged 7 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,10 @@ docker-image-tag: ## prints the docker image tag
docker-publish-app: app-build-prod ## publish DSP-APP Docker image to Docker-Hub for AMD64 and ARM64
docker buildx build --platform linux/amd64,linux/arm64/v8 --build-arg build_tag=$(BUILD_TAG) -t $(DSP_APP_IMAGE) --push .
# publish source maps to DataDog
# datadog-ci sourcemaps upload ${CURRENT_DIR}/dist/apps/dsp-app \
# --service=dsp-app \
# --release-version=${BUILD_TAG} \
# --minified-path-prefix=/

npx datadog-ci sourcemaps upload ${CURRENT_DIR}/dist/apps/dsp-app \
--service=dsp-app \
--release-version=${BUILD_TAG} \
--minified-path-prefix=/

.PHONY: docker-publish
docker-publish: docker-publish-app ## publish all Docker images in the monorepo.
Expand Down
9 changes: 9 additions & 0 deletions apps/dsp-app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ import { CommentFormComponent } from './workspace/resource/values/comment-form/c
import { DataModelsComponent } from './project/data-models/data-models.component';
import { ResourceClassPropertyInfoComponent } from '@dsp-app/src/app/project/ontology/resource-class-info/resource-class-property-info/resource-class-property-info.component';
import { AppLoggingService } from '@dasch-swiss/vre/shared/app-logging';
import {
buildTagFactory,
BuildTagToken,
} from '@dasch-swiss/vre/shared/app-config';

// translate: AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
Expand Down Expand Up @@ -378,6 +382,11 @@ export function httpLoaderFactory(httpClient: HttpClient) {
appConfigService.dspInstrumentationConfig,
deps: [AppConfigService],
},
{
provide: BuildTagToken,
useFactory: buildTagFactory,
deps: [HttpClient],
},
{
provide: ErrorHandler,
useClass: AppErrorHandler,
Expand Down
94 changes: 49 additions & 45 deletions apps/dsp-app/src/app/main/services/datadog-rum.service.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,67 @@
import { Inject, Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import {
datadogRum,
RumFetchResourceEventDomainContext,
} from '@datadog/browser-rum';
import {
BuildTag,
BuildTagToken,
DspInstrumentationConfig,
DspInstrumentationToken,
} from '@dasch-swiss/vre/shared/app-config';
import { Session, SessionService } from './session.service';
import { environment } from '@dsp-app/src/environments/environment';
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
})
export class DatadogRumService {
constructor(
@Inject(DspInstrumentationToken)
private _c: DspInstrumentationConfig,
private _s: SessionService
) {
if (this._c.dataDog.enabled) {
datadogRum.init({
applicationId: this._c.dataDog.applicationId,
clientToken: this._c.dataDog.clientToken,
site: this._c.dataDog.site,
service: this._c.dataDog.service,
env: this._c.environment,
version: environment.version,
sessionSampleRate: 100,
sessionReplaySampleRate: 100, // if not included, the default is 100
trackResources: true,
trackLongTasks: true,
trackUserInteractions: true,
trackFrustrations: true,
useSecureSessionCookie: true,
beforeSend: (event, context) => {
// collect a RUM resource's response headers
if (
event.type === 'resource' &&
event.resource.type === 'xhr'
) {
event.context = {
...event.context,
responseHeaders: (
context as RumFetchResourceEventDomainContext
).response?.body,
};
}
},
});
private buildTag$: Observable<BuildTag> = inject(BuildTagToken);
private config: DspInstrumentationConfig = inject(DspInstrumentationToken);
private session = inject(SessionService);
constructor() {
this.buildTag$.pipe(first()).subscribe((tag) => {
if (this.config.dataDog.enabled) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you unsubscribe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first does it automatically after the first value is received.

datadogRum.init({
applicationId: this.config.dataDog.applicationId,
clientToken: this.config.dataDog.clientToken,
site: this.config.dataDog.site,
service: this.config.dataDog.service,
env: this.config.environment,
version: tag.build_tag,
sessionSampleRate: 100,
sessionReplaySampleRate: 100, // if not included, the default is 100
trackResources: true,
trackLongTasks: true,
trackUserInteractions: true,
trackFrustrations: true,
useSecureSessionCookie: true,
beforeSend: (event, context) => {
// collect a RUM resource's response headers
if (
event.type === 'resource' &&
event.resource.type === 'xhr'
) {
event.context = {
...event.context,
responseHeaders: (
context as RumFetchResourceEventDomainContext
).response?.body,
};
}
},
});

// if session is valid: setActiveUser
this._s.isSessionValid().subscribe((response: boolean) => {
if (response) {
const session: Session = this._s.getSession();
this.setActiveUser(session.user.name, 'username');
}
});
}
// if session is valid: setActiveUser
this.session.isSessionValid().subscribe((response: boolean) => {
if (response) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you unsubscribe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nowhere. This is already existing code and out of scope for this round.

const session: Session = this.session.getSession();
this.setActiveUser(session.user.name, 'username');
}
});
}
});
}

setActiveUser(
Expand Down
10 changes: 5 additions & 5 deletions apps/dsp-app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ if (environment.production) {
enableProdMode();
}

const request = new XMLHttpRequest();
request.addEventListener('load', configListener);
request.addEventListener('error', configFailed);
request.open('GET', `./config/config.${environment.name}.json`);
request.send();
const getConfigRequest = new XMLHttpRequest();
getConfigRequest.addEventListener('load', configListener);
getConfigRequest.addEventListener('error', configFailed);
getConfigRequest.open('GET', `./config/config.${environment.name}.json`);
getConfigRequest.send();
2 changes: 2 additions & 0 deletions libs/vre/shared/app-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from './lib/app-config/dsp-app-config';
export * from './lib/app-config/dsp-config';
export * from './lib/app-config/dsp-iiif-config';
export * from './lib/app-config/dsp-instrumentation-config';
export * from './lib/build-tag/build-tag-token';
export * from './lib/build-tag/build-tag';
17 changes: 17 additions & 0 deletions libs/vre/shared/app-config/src/lib/build-tag/build-tag-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

import { InjectionToken } from '@angular/core';
import { BuildTag } from './build-tag';
import { Observable } from 'rxjs';

/**
* The BuildTagToken is used to encapsulate build_tag
* loaded from 'config/build.json' loaded in main.ts before bootstrap.
* As such, the structure of the loaded JSON is at this point not checked.
*/
export const BuildTagToken = new InjectionToken<Observable<BuildTag>>(
'A stream with the current build tag'
);
24 changes: 24 additions & 0 deletions libs/vre/shared/app-config/src/lib/build-tag/build-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

import { z } from 'zod';
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

export const BuildTagSchema = z.object({
build_tag: z.string().nonempty(),
});

export type BuildTag = z.infer<typeof BuildTagSchema>;

export function buildTagFactory(): Observable<BuildTag> {
const httpClient = inject(HttpClient);

return httpClient
.get('/config/build.json')
.pipe(map((buildTagValue) => BuildTagSchema.parse(buildTagValue)));
}
2 changes: 1 addition & 1 deletion libs/vre/shared/app-error-handler/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './lib/app-error-handler/app-error-handler';
export * from './lib/app-error-handler';
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import { ErrorHandler, Injector } from '@angular/core';
/*
* Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

import { ErrorHandler, inject } from '@angular/core';
import { AppLoggingService } from '@dasch-swiss/vre/shared/app-logging';
import { HttpErrorResponse } from '@angular/common/http';

export class AppErrorHandler implements ErrorHandler {
constructor(private _injector: Injector) {}

logger = inject(AppLoggingService);
/**
* Logs out the error using the logging service.
* @param error the error to log.
*/
handleError(error: Error): void {
const logger = this._injector.get(AppLoggingService);

if (error instanceof HttpErrorResponse) {
// HTTP related error
logger.error('Caught HttpErrorResponse error', error);
this.logger.error('Caught HttpErrorResponse error', error);
} else if (
error instanceof TypeError ||
error instanceof ReferenceError
) {
// Runtime exceptions mostly induced by Developer's code
logger.error('Caught Type or Reference error', error);
this.logger.error('Caught Type or Reference error', error);
} else {
// catch-all: catch rest of errors
logger.error('Caught error', error);
this.logger.error('Caught error', error);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,45 @@
import { Inject, Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { datadogLogs, Logger } from '@datadog/browser-logs';
import {
BuildTag,
BuildTagToken,
DspInstrumentationConfig,
DspInstrumentationToken,
} from '@dasch-swiss/vre/shared/app-config';
import { Observable } from 'rxjs';

import { first } from 'rxjs/operators';

@Injectable({
providedIn: 'root',
})
export class AppLoggingService {
private logger: Logger | undefined;
constructor(
@Inject(DspInstrumentationToken) private c: DspInstrumentationConfig
) {
if (c.dataDog.enabled && typeof c.dataDog.clientToken == 'string') {
datadogLogs.init({
clientToken: c.dataDog.clientToken,
site: c.dataDog.site,
service: c.dataDog.service,
env: c.environment,
forwardErrorsToLogs: true, // forwards console.error logs, uncaught exceptions and network errors to Datadog
forwardConsoleLogs: [], // don't forward any logs (besides console.error - in previous setting) to Datadog
useSecureSessionCookie: true,
});
datadogLogs.logger.setHandler(['console', 'http']);
this.logger = datadogLogs.logger;
}
private buildTag$: Observable<BuildTag> = inject(BuildTagToken);
private config: DspInstrumentationConfig = inject(DspInstrumentationToken);

constructor() {
this.buildTag$.pipe(first()).subscribe((tag) => {
if (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you unsubscribe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first()

this.config.dataDog.enabled &&
typeof this.config.dataDog.clientToken == 'string'
) {
datadogLogs.init({
clientToken: this.config.dataDog.clientToken,
site: this.config.dataDog.site,
service: this.config.dataDog.service,
env: this.config.environment,
version: tag.build_tag,
forwardErrorsToLogs: true, // forwards console.error logs, uncaught exceptions and network errors to Datadog
forwardConsoleLogs: [], // don't forward any logs (besides console.error - in previous setting) to Datadog
useSecureSessionCookie: true,
});
datadogLogs.logger.setHandler(['console', 'http']);
this.logger = datadogLogs.logger;
}
this.info('config', this.config);
this.info('build_tag', tag);
});
}

debug(message: string, messageContext?: object, error?: Error) {
Expand Down