posthog-node 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/index.ts +1 -0
- package/lib/index.cjs.js +911 -7
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +44 -3
- package/lib/index.esm.js +911 -7
- package/lib/index.esm.js.map +1 -1
- package/lib/posthog-core/src/index.d.ts +6 -0
- package/lib/posthog-core/src/types.d.ts +1 -0
- package/lib/posthog-core/src/utils.d.ts +2 -0
- package/lib/posthog-node/index.d.ts +1 -0
- package/lib/posthog-node/src/error-tracking.d.ts +12 -0
- package/lib/posthog-node/src/extensions/error-tracking/autocapture.d.ts +3 -0
- package/lib/posthog-node/src/extensions/error-tracking/context-lines.d.ts +4 -0
- package/lib/posthog-node/src/extensions/error-tracking/error-conversion.d.ts +5 -0
- package/lib/posthog-node/src/extensions/error-tracking/reduceable-cache.d.ts +12 -0
- package/lib/posthog-node/src/extensions/error-tracking/stack-trace.d.ts +15 -0
- package/lib/posthog-node/src/extensions/error-tracking/type-checking.d.ts +7 -0
- package/lib/posthog-node/src/extensions/error-tracking/types.d.ts +57 -0
- package/lib/posthog-node/src/extensions/express.d.ts +17 -0
- package/lib/posthog-node/src/extensions/sentry-integration.d.ts +1 -2
- package/lib/posthog-node/src/fetch.d.ts +1 -2
- package/lib/posthog-node/src/posthog-node.d.ts +5 -0
- package/package.json +1 -1
- package/src/error-tracking.ts +66 -0
- package/src/extensions/error-tracking/autocapture.ts +62 -0
- package/src/extensions/error-tracking/context-lines.ts +389 -0
- package/src/extensions/error-tracking/error-conversion.ts +250 -0
- package/src/extensions/error-tracking/reduceable-cache.ts +36 -0
- package/src/extensions/error-tracking/stack-trace.ts +269 -0
- package/src/extensions/error-tracking/type-checking.ts +37 -0
- package/src/extensions/error-tracking/types.ts +62 -0
- package/src/extensions/express.ts +37 -0
- package/src/extensions/sentry-integration.ts +1 -3
- package/src/fetch.ts +3 -7
- package/src/posthog-node.ts +10 -0
|
@@ -183,6 +183,12 @@ export declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
183
183
|
onFeatureFlags(cb: (flags: PostHogDecideResponse['featureFlags']) => void): () => void;
|
|
184
184
|
onFeatureFlag(key: string, cb: (value: string | boolean) => void): () => void;
|
|
185
185
|
overrideFeatureFlag(flags: PostHogDecideResponse['featureFlags'] | null): Promise<void>;
|
|
186
|
+
/***
|
|
187
|
+
*** ERROR TRACKING
|
|
188
|
+
***/
|
|
189
|
+
captureException(error: Error, additionalProperties?: {
|
|
190
|
+
[key: string]: any;
|
|
191
|
+
}): void;
|
|
186
192
|
}
|
|
187
193
|
export * from './types';
|
|
188
194
|
export { LZString };
|
|
@@ -130,3 +130,4 @@ export type PostHogFlagsAndPayloadsResponse = {
|
|
|
130
130
|
export type JsonType = string | number | boolean | null | {
|
|
131
131
|
[key: string]: JsonType;
|
|
132
132
|
} | Array<JsonType>;
|
|
133
|
+
export type FetchLike = (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FetchLike } from './types';
|
|
1
2
|
export declare function assert(truthyValue: any, message: string): void;
|
|
2
3
|
export declare function removeTrailingSlash(url: string): string;
|
|
3
4
|
export interface RetriableOptions {
|
|
@@ -10,3 +11,4 @@ export declare function currentTimestamp(): number;
|
|
|
10
11
|
export declare function currentISOTime(): string;
|
|
11
12
|
export declare function safeSetTimeout(fn: () => void, timeout: number): any;
|
|
12
13
|
export declare const isPromise: (obj: any) => obj is Promise<any>;
|
|
14
|
+
export declare function getFetch(): FetchLike | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EventHint } from './extensions/error-tracking/types';
|
|
2
|
+
import { PostHog, PostHogOptions } from './posthog-node';
|
|
3
|
+
export default class ErrorTracking {
|
|
4
|
+
private client;
|
|
5
|
+
private _exceptionAutocaptureEnabled;
|
|
6
|
+
static captureException(client: PostHog, error: unknown, distinctId: string, hint: EventHint, additionalProperties?: Record<string | number, any>): Promise<void>;
|
|
7
|
+
constructor(client: PostHog, options: PostHogOptions);
|
|
8
|
+
private startAutocaptureIfEnabled;
|
|
9
|
+
private onException;
|
|
10
|
+
private onFatalError;
|
|
11
|
+
isEnabled(): boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { EventHint } from 'posthog-node/src/extensions/error-tracking/types';
|
|
2
|
+
export declare function addUncaughtExceptionListener(captureFn: (exception: Error, hint: EventHint) => void, onFatalFn: () => void): void;
|
|
3
|
+
export declare function addUnhandledRejectionListener(captureFn: (exception: unknown, hint: EventHint) => void): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ErrorProperties, EventHint, StackParser } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* based on the very wonderful MIT licensed Sentry SDK
|
|
4
|
+
*/
|
|
5
|
+
export declare function propertiesFromUnknownInput(stackParser: StackParser, input: unknown, hint?: EventHint): Promise<ErrorProperties>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A simple Least Recently Used map */
|
|
2
|
+
export declare class ReduceableCache<K, V> {
|
|
3
|
+
private readonly _maxSize;
|
|
4
|
+
private readonly _cache;
|
|
5
|
+
constructor(_maxSize: number);
|
|
6
|
+
/** Get an entry or undefined if it was not in the cache. Re-inserts to update the recently used order */
|
|
7
|
+
get(key: K): V | undefined;
|
|
8
|
+
/** Insert an entry and evict an older entry if we've reached maxSize */
|
|
9
|
+
set(key: K, value: V): void;
|
|
10
|
+
/** Remove an entry and return the entry if it was in the cache */
|
|
11
|
+
reduce(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StackFrame, StackLineParser, StackLineParserFn, StackParser } from './types';
|
|
2
|
+
type GetModuleFn = (filename: string | undefined) => string | undefined;
|
|
3
|
+
/** Node Stack line parser */
|
|
4
|
+
export declare function node(getModule?: GetModuleFn): StackLineParserFn;
|
|
5
|
+
/**
|
|
6
|
+
* Does this filename look like it's part of the app code?
|
|
7
|
+
*/
|
|
8
|
+
export declare function filenameIsInApp(filename: string, isNative?: boolean): boolean;
|
|
9
|
+
export declare function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser;
|
|
10
|
+
export declare const defaultStackParser: StackParser;
|
|
11
|
+
/** Creates a function that gets the module name from a filename */
|
|
12
|
+
export declare function createGetModuleFromFilename(basePath?: string, isWindows?: boolean): (filename: string | undefined) => string | undefined;
|
|
13
|
+
export declare function createStackParser(...parsers: StackLineParser[]): StackParser;
|
|
14
|
+
export declare function reverseAndStripFrames(stack: ReadonlyArray<StackFrame>): StackFrame[];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PolymorphicEvent } from './types';
|
|
2
|
+
export declare function isEvent(candidate: unknown): candidate is PolymorphicEvent;
|
|
3
|
+
export declare function isPlainObject(candidate: unknown): candidate is Record<string, unknown>;
|
|
4
|
+
export declare function isError(candidate: unknown): candidate is Error;
|
|
5
|
+
export declare function isInstanceOf(candidate: unknown, base: any): boolean;
|
|
6
|
+
export declare function isErrorEvent(event: unknown): boolean;
|
|
7
|
+
export declare function isBuiltin(candidate: unknown, className: string): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export declare const severityLevels: readonly ["fatal", "error", "warning", "log", "info", "debug"];
|
|
2
|
+
export declare type SeverityLevel = (typeof severityLevels)[number];
|
|
3
|
+
export interface PolymorphicEvent {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
readonly type: string;
|
|
6
|
+
readonly target?: unknown;
|
|
7
|
+
readonly currentTarget?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface EventHint {
|
|
10
|
+
mechanism?: Partial<Mechanism>;
|
|
11
|
+
syntheticException?: Error | null;
|
|
12
|
+
}
|
|
13
|
+
export interface ErrorProperties {
|
|
14
|
+
$exception_list: Exception[];
|
|
15
|
+
$exception_level?: SeverityLevel;
|
|
16
|
+
$exception_DOMException_code?: string;
|
|
17
|
+
$exception_personURL?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Exception {
|
|
20
|
+
type?: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
mechanism?: Mechanism;
|
|
23
|
+
module?: string;
|
|
24
|
+
thread_id?: number;
|
|
25
|
+
stacktrace?: {
|
|
26
|
+
frames?: StackFrame[];
|
|
27
|
+
type: 'raw';
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface Mechanism {
|
|
31
|
+
handled?: boolean;
|
|
32
|
+
type?: string;
|
|
33
|
+
source?: string;
|
|
34
|
+
synthetic?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export type StackParser = (stack: string, skipFirstLines?: number) => StackFrame[];
|
|
37
|
+
export type StackLineParserFn = (line: string) => StackFrame | undefined;
|
|
38
|
+
export type StackLineParser = [number, StackLineParserFn];
|
|
39
|
+
export interface StackFrame {
|
|
40
|
+
platform: string;
|
|
41
|
+
filename?: string;
|
|
42
|
+
function?: string;
|
|
43
|
+
module?: string;
|
|
44
|
+
lineno?: number;
|
|
45
|
+
colno?: number;
|
|
46
|
+
abs_path?: string;
|
|
47
|
+
context_line?: string;
|
|
48
|
+
pre_context?: string[];
|
|
49
|
+
post_context?: string[];
|
|
50
|
+
in_app?: boolean;
|
|
51
|
+
instruction_addr?: string;
|
|
52
|
+
addr_mode?: string;
|
|
53
|
+
vars?: {
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
};
|
|
56
|
+
debug_id?: string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type * as http from 'node:http';
|
|
3
|
+
import { PostHog } from '../posthog-node';
|
|
4
|
+
type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void;
|
|
5
|
+
type ExpressErrorMiddleware = (error: MiddlewareError, req: http.IncomingMessage, res: http.ServerResponse, next: (error: MiddlewareError) => void) => void;
|
|
6
|
+
interface MiddlewareError extends Error {
|
|
7
|
+
status?: number | string;
|
|
8
|
+
statusCode?: number | string;
|
|
9
|
+
status_code?: number | string;
|
|
10
|
+
output?: {
|
|
11
|
+
statusCode?: number | string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function setupExpressErrorHandler(_posthog: PostHog, app: {
|
|
15
|
+
use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown;
|
|
16
|
+
}): void;
|
|
17
|
+
export {};
|
|
@@ -21,9 +21,8 @@
|
|
|
21
21
|
* @param {string} [prefix] Optional: Url of a self-hosted sentry instance (default: https://sentry.io/organizations/)
|
|
22
22
|
* @param {SeverityLevel[] | '*'} [severityAllowList] Optional: send events matching the provided levels. Use '*' to send all events (default: ['error'])
|
|
23
23
|
*/
|
|
24
|
+
import { SeverityLevel } from 'posthog-node/src/extensions/error-tracking/types';
|
|
24
25
|
import { type PostHog } from '../posthog-node';
|
|
25
|
-
export declare const severityLevels: readonly ["fatal", "error", "warning", "log", "info", "debug"];
|
|
26
|
-
export declare type SeverityLevel = (typeof severityLevels)[number];
|
|
27
26
|
type _SentryEvent = any;
|
|
28
27
|
type _SentryEventProcessor = any;
|
|
29
28
|
type _SentryHub = any;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* This is currently solved by using the global fetch if available instead.
|
|
7
7
|
* See https://github.com/PostHog/posthog-js-lite/issues/127 for more info
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
type FetchLike = (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
|
|
9
|
+
import { FetchLike } from 'posthog-core/src';
|
|
11
10
|
declare const _default: FetchLike;
|
|
12
11
|
export default _default;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { JsonType, PostHogCoreOptions, PostHogCoreStateless, PostHogFetchOptions, PostHogFetchResponse, PostHogFlagsAndPayloadsResponse, PostHogPersistedProperty } from '../../posthog-core/src';
|
|
2
2
|
import { EventMessage, GroupIdentifyMessage, IdentifyMessage, PostHogNodeV1 } from './types';
|
|
3
|
+
import ErrorTracking from './error-tracking';
|
|
3
4
|
export type PostHogOptions = PostHogCoreOptions & {
|
|
4
5
|
persistence?: 'memory';
|
|
5
6
|
personalApiKey?: string;
|
|
7
|
+
privacyMode?: boolean;
|
|
8
|
+
enableExceptionAutocapture?: boolean;
|
|
6
9
|
featureFlagsPollingInterval?: number;
|
|
7
10
|
maxCacheSize?: number;
|
|
8
11
|
fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>;
|
|
@@ -10,6 +13,7 @@ export type PostHogOptions = PostHogCoreOptions & {
|
|
|
10
13
|
export declare class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
|
|
11
14
|
private _memoryStorage;
|
|
12
15
|
private featureFlagsPoller?;
|
|
16
|
+
protected errorTracking: ErrorTracking;
|
|
13
17
|
private maxCacheSize;
|
|
14
18
|
readonly options: PostHogOptions;
|
|
15
19
|
distinctIdHasSentFlagCalls: Record<string, string[]>;
|
|
@@ -72,4 +76,5 @@ export declare class PostHog extends PostHogCoreStateless implements PostHogNode
|
|
|
72
76
|
reloadFeatureFlags(): Promise<void>;
|
|
73
77
|
shutdown(shutdownTimeoutMs?: number): Promise<void>;
|
|
74
78
|
private addLocalPersonAndGroupProperties;
|
|
79
|
+
captureException(error: unknown, distinctId: string, additionalProperties?: Record<string | number, any>): void;
|
|
75
80
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { EventHint } from './extensions/error-tracking/types'
|
|
2
|
+
import { addUncaughtExceptionListener, addUnhandledRejectionListener } from './extensions/error-tracking/autocapture'
|
|
3
|
+
import { PostHog, PostHogOptions } from './posthog-node'
|
|
4
|
+
import { uuidv7 } from 'posthog-core/src/vendor/uuidv7'
|
|
5
|
+
import { propertiesFromUnknownInput } from './extensions/error-tracking/error-conversion'
|
|
6
|
+
import { EventMessage } from './types'
|
|
7
|
+
import { defaultStackParser } from './extensions/error-tracking/stack-trace'
|
|
8
|
+
|
|
9
|
+
const SHUTDOWN_TIMEOUT = 2000
|
|
10
|
+
|
|
11
|
+
export default class ErrorTracking {
|
|
12
|
+
private client: PostHog
|
|
13
|
+
private _exceptionAutocaptureEnabled: boolean
|
|
14
|
+
|
|
15
|
+
static async captureException(
|
|
16
|
+
client: PostHog,
|
|
17
|
+
error: unknown,
|
|
18
|
+
distinctId: string,
|
|
19
|
+
hint: EventHint,
|
|
20
|
+
additionalProperties?: Record<string | number, any>
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const properties: EventMessage['properties'] = { ...additionalProperties }
|
|
23
|
+
if (!distinctId) {
|
|
24
|
+
properties.$process_person_profile = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const exceptionProperties = await propertiesFromUnknownInput(defaultStackParser, error, hint)
|
|
28
|
+
|
|
29
|
+
client.capture({
|
|
30
|
+
event: '$exception',
|
|
31
|
+
distinctId: distinctId || uuidv7(),
|
|
32
|
+
properties: {
|
|
33
|
+
...exceptionProperties,
|
|
34
|
+
...properties,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(client: PostHog, options: PostHogOptions) {
|
|
40
|
+
this.client = client
|
|
41
|
+
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false
|
|
42
|
+
|
|
43
|
+
this.startAutocaptureIfEnabled()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private startAutocaptureIfEnabled(): void {
|
|
47
|
+
if (this.isEnabled()) {
|
|
48
|
+
addUncaughtExceptionListener(this.onException.bind(this), this.onFatalError.bind(this))
|
|
49
|
+
addUnhandledRejectionListener(this.onException.bind(this))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private onException(exception: unknown, hint: EventHint): void {
|
|
54
|
+
// Given stateless nature of Node SDK we capture exceptions using personless processing
|
|
55
|
+
// when no user can be determined e.g. in the case of exception autocapture
|
|
56
|
+
ErrorTracking.captureException(this.client, exception, uuidv7(), hint, { $process_person_profile: false })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async onFatalError(): Promise<void> {
|
|
60
|
+
await this.client.shutdown(SHUTDOWN_TIMEOUT)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
isEnabled(): boolean {
|
|
64
|
+
return !this.client.isDisabled && this._exceptionAutocaptureEnabled
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EventHint } from 'posthog-node/src/extensions/error-tracking/types'
|
|
2
|
+
|
|
3
|
+
type ErrorHandler = { _posthogErrorHandler: boolean } & ((error: Error) => void)
|
|
4
|
+
|
|
5
|
+
function makeUncaughtExceptionHandler(
|
|
6
|
+
captureFn: (exception: Error, hint: EventHint) => void,
|
|
7
|
+
onFatalFn: () => void
|
|
8
|
+
): ErrorHandler {
|
|
9
|
+
let calledFatalError: boolean = false
|
|
10
|
+
|
|
11
|
+
return Object.assign(
|
|
12
|
+
(error: Error): void => {
|
|
13
|
+
// Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
|
|
14
|
+
// want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
|
|
15
|
+
// exit behaviour of the SDK accordingly:
|
|
16
|
+
// - If other listeners are attached, do not exit.
|
|
17
|
+
// - If the only listener attached is ours, exit.
|
|
18
|
+
const userProvidedListenersCount = global.process.listeners('uncaughtException').filter((listener) => {
|
|
19
|
+
// There are 2 listeners we ignore:
|
|
20
|
+
return (
|
|
21
|
+
// as soon as we're using domains this listener is attached by node itself
|
|
22
|
+
listener.name !== 'domainUncaughtExceptionClear' &&
|
|
23
|
+
// the handler we register in this integration
|
|
24
|
+
(listener as ErrorHandler)._posthogErrorHandler !== true
|
|
25
|
+
)
|
|
26
|
+
}).length
|
|
27
|
+
|
|
28
|
+
const processWouldExit = userProvidedListenersCount === 0
|
|
29
|
+
|
|
30
|
+
captureFn(error, {
|
|
31
|
+
mechanism: {
|
|
32
|
+
type: 'onuncaughtexception',
|
|
33
|
+
handled: false,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (!calledFatalError && processWouldExit) {
|
|
38
|
+
calledFatalError = true
|
|
39
|
+
onFatalFn()
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ _posthogErrorHandler: true }
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function addUncaughtExceptionListener(
|
|
47
|
+
captureFn: (exception: Error, hint: EventHint) => void,
|
|
48
|
+
onFatalFn: () => void
|
|
49
|
+
): void {
|
|
50
|
+
global.process.on('uncaughtException', makeUncaughtExceptionHandler(captureFn, onFatalFn))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function addUnhandledRejectionListener(captureFn: (exception: unknown, hint: EventHint) => void): void {
|
|
54
|
+
global.process.on('unhandledRejection', (reason: unknown) => {
|
|
55
|
+
captureFn(reason, {
|
|
56
|
+
mechanism: {
|
|
57
|
+
type: 'onunhandledrejection',
|
|
58
|
+
handled: false,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|