Files

178 lines
5.9 KiB
Plaintext

---
title: Angular error tracking installation
showStepsToc: true
---
import { Steps, Step } from 'components/Docs/Steps'
import InstallWebPackageManagers from "../../integrate/_snippets/install-web-package-managers.mdx"
import InstallAngular from "../../integrate/_snippets/install-angular.mdx"
import StepVerifyErrorTracking from "./_snippets/step-verify-error-tracking.mdx"
import StepUploadSourceMaps from "./_snippets/step-upload-source-maps.tsx"
<Steps>
<Step title="Installing PostHog Angular SDK" subtitle="Your goal in this step: Install the PostHog Angular SDK." badge="required">
<InstallAngular/>
</Step>
<Step checkpoint title="Verify PostHog is initialized" subtitle="Confirm you can capture events with PostHog">
Before proceeding, confirm that you can capture events with PostHog using `posthog.capture('test_event')`.
</Step>
<Step title="Setting up exception autocapture" subtitle="Your goal in this step: Enable automatic exception tracking for your Angular application." badge="recommended">
Exception autocapture can be enabled during initialization of the PostHog client to automatically capture any exception thrown by your Angular application.
This requires overriding Angular's default `ErrorHandler` provider:
```ts file=src/app/posthog-error-handler.ts
import { ErrorHandler, Injectable, Provider } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import posthog from 'posthog-js';
/**
* Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one.
*/
@Injectable({ providedIn: 'root' })
class PostHogErrorHandler implements ErrorHandler {
public constructor() {}
/**
* Method called for every value captured through the ErrorHandler
*/
public handleError(error: unknown): void {
const extractedError = this._extractError(error) || 'Unknown error';
runOutsideAngular(() => posthog.captureException(extractedError));
}
protected _extractError(errorCandidate: unknown): unknown {
const error = tryToUnwrapZonejsError(errorCandidate);
if (error instanceof HttpErrorResponse) {
return extractHttpModuleError(error);
}
if (typeof error === 'string' || isErrorOrErrorLikeObject(error)) {
return error;
}
return null;
}
}
// https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts
function tryToUnwrapZonejsError(error: unknown): unknown | Error {
// TODO: once Angular14 is the minimum requirement ERROR_ORIGINAL_ERROR and
// getOriginalError from error.ts can be used directly.
return error && (error as { ngOriginalError: Error }).ngOriginalError
? (error as { ngOriginalError: Error }).ngOriginalError
: error;
}
function extractHttpModuleError(error: HttpErrorResponse): string | Error {
// The `error` property of http exception can be either an `Error` object, which we can use directly...
if (isErrorOrErrorLikeObject(error.error)) {
return error.error;
}
// ... or an`ErrorEvent`, which can provide us with the message but no stack...
// guarding `ErrorEvent` against `undefined` as it's not defined in Node environments
if (
typeof ErrorEvent !== 'undefined' &&
error.error instanceof ErrorEvent &&
error.error.message
) {
return error.error.message;
}
// ...or the request body itself, which we can use as a message instead.
if (typeof error.error === 'string') {
return `Server returned code ${error.status} with body "${error.error}"`;
}
// If we don't have any detailed information, fallback to the request message itself.
return error.message;
}
function isErrorOrErrorLikeObject(value: unknown): value is Error {
if (value instanceof Error) {
return true;
}
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return false;
}
return 'name' in value && 'message' in value && 'stack' in value;
}
// This would be exposed in the global environment whenever `zone.js` is
// included in the `polyfills` configuration property. Starting from Angular 17,
// users can opt-in to use zoneless change detection.
declare const Zone: any;
// In Angular 17 and future versions, zoneless support is forthcoming.
// Therefore, it's advisable to safely check whether the `run` function is
// available in the `<root>` context.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const isNgZoneEnabled = typeof Zone !== 'undefined' && Zone.root?.run;
export function runOutsideAngular<T>(callback: () => T): T {
// Running the `callback` within the root execution context enables Angular
// processes (such as SSR and hydration) to continue functioning normally without
// timeouts and delays that could affect the user experience. This approach is
// necessary because some of the Sentry functionality continues to run in the background.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return isNgZoneEnabled ? Zone.root.run(callback) : callback();
}
export function providePostHogErrorHandler(): Provider {
return {
provide: ErrorHandler,
useValue: new PostHogErrorHandler(),
};
}
```
Then, in your `src/app/app.config.ts`, import the `providePostHogErrorHandler` function and add it to the providers array:
```ts file=src/app/app.config.ts
// src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { providePostHogErrorHandler } from './posthog-error-handler'; // +
export const appConfig: ApplicationConfig = {
providers: [
...
providePostHogErrorHandler(), // +
],
};
```
If there are more errors you'd like to capture, you can manually call the `captureException` method:
```ts
posthog.captureException(e, additionalProperties)
```
</Step>
<StepVerifyErrorTracking />
<Step title="Upload source maps" badge="required">
<StepUploadSourceMaps urlPath="angular" framework="Angular" />
</Step>
</Steps>