posthog-node 4.16.0 → 4.17.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 +4 -0
- package/lib/edge/index.cjs.js +3919 -0
- package/lib/edge/index.cjs.js.map +1 -0
- package/lib/edge/index.esm.js +3893 -0
- package/lib/edge/index.esm.js.map +1 -0
- package/lib/index.d.ts +921 -859
- package/lib/{index.cjs.js → node/index.cjs.js} +3575 -3594
- package/lib/node/index.cjs.js.map +1 -0
- package/lib/{index.esm.js → node/index.esm.js} +3576 -3592
- package/lib/node/index.esm.js.map +1 -0
- package/package.json +31 -4
- package/src/{posthog-node.ts → client.ts} +10 -28
- package/src/entrypoints/index.edge.ts +15 -0
- package/src/entrypoints/index.node.ts +17 -0
- package/src/exports.ts +3 -0
- package/src/extensions/error-tracking/autocapture.ts +1 -1
- package/src/extensions/error-tracking/{context-lines.ts → context-lines.node.ts} +64 -97
- package/src/extensions/error-tracking/error-conversion.ts +22 -5
- package/src/extensions/error-tracking/get-module.node.ts +57 -0
- package/src/{error-tracking.ts → extensions/error-tracking/index.ts} +12 -10
- package/src/extensions/error-tracking/{stack-trace.ts → stack-parser.ts} +7 -64
- package/src/extensions/error-tracking/types.ts +4 -0
- package/src/extensions/express.ts +3 -3
- package/src/{crypto-helpers.ts → extensions/feature-flags/crypto-helpers.ts} +1 -1
- package/src/{feature-flags.ts → extensions/feature-flags/feature-flags.ts} +7 -6
- package/src/extensions/sentry-integration.ts +13 -5
- package/src/fetch.ts +2 -2
- package/src/storage-memory.ts +13 -0
- package/src/types.ts +19 -1
- package/test/crypto.spec.ts +2 -2
- package/test/extensions/error-conversion.spec.ts +2 -2
- package/test/extensions/sentry-integration.spec.ts +1 -2
- package/test/feature-flags.decide.spec.ts +2 -1
- package/test/feature-flags.spec.ts +7 -4
- package/test/lazy.spec.ts +1 -1
- package/test/posthog-node.spec.ts +3 -3
- package/tsconfig.json +1 -0
- package/index.ts +0 -3
- package/lib/index.cjs.js.map +0 -1
- package/lib/index.esm.js.map +0 -1
- package/lib/posthog-core/src/eventemitter.d.ts +0 -8
- package/lib/posthog-core/src/featureFlagUtils.d.ts +0 -34
- package/lib/posthog-core/src/index.d.ts +0 -259
- package/lib/posthog-core/src/lz-string.d.ts +0 -8
- package/lib/posthog-core/src/storage-memory.d.ts +0 -6
- package/lib/posthog-core/src/types.d.ts +0 -422
- package/lib/posthog-core/src/utils.d.ts +0 -20
- package/lib/posthog-core/src/vendor/uuidv7.d.ts +0 -179
- package/lib/posthog-node/index.d.ts +0 -3
- package/lib/posthog-node/src/crypto-helpers.d.ts +0 -3
- package/lib/posthog-node/src/crypto.d.ts +0 -2
- package/lib/posthog-node/src/error-tracking.d.ts +0 -12
- package/lib/posthog-node/src/extensions/error-tracking/autocapture.d.ts +0 -3
- package/lib/posthog-node/src/extensions/error-tracking/context-lines.d.ts +0 -6
- package/lib/posthog-node/src/extensions/error-tracking/error-conversion.d.ts +0 -2
- package/lib/posthog-node/src/extensions/error-tracking/reduceable-cache.d.ts +0 -12
- package/lib/posthog-node/src/extensions/error-tracking/stack-trace.d.ts +0 -15
- package/lib/posthog-node/src/extensions/error-tracking/type-checking.d.ts +0 -7
- package/lib/posthog-node/src/extensions/error-tracking/types.d.ts +0 -57
- package/lib/posthog-node/src/extensions/express.d.ts +0 -17
- package/lib/posthog-node/src/extensions/sentry-integration.d.ts +0 -51
- package/lib/posthog-node/src/feature-flags.d.ts +0 -84
- package/lib/posthog-node/src/fetch.d.ts +0 -11
- package/lib/posthog-node/src/lazy.d.ts +0 -23
- package/lib/posthog-node/src/posthog-node.d.ts +0 -98
- package/lib/posthog-node/src/types.d.ts +0 -229
- package/lib/posthog-node/test/test-utils.d.ts +0 -18
- /package/src/{crypto.ts → extensions/feature-flags/crypto.ts} +0 -0
- /package/src/{lazy.ts → extensions/feature-flags/lazy.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posthog-node",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.17.0",
|
|
4
4
|
"description": "PostHog Node.js integration",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"email": "hey@posthog.com",
|
|
20
20
|
"url": "https://posthog.com"
|
|
21
21
|
},
|
|
22
|
-
"main": "lib/index.cjs.js",
|
|
23
|
-
"module": "lib/index.esm.js",
|
|
22
|
+
"main": "lib/node/index.cjs.js",
|
|
23
|
+
"module": "lib/node/index.esm.js",
|
|
24
24
|
"types": "lib/index.d.ts",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"axios": "^1.8.2"
|
|
@@ -35,5 +35,32 @@
|
|
|
35
35
|
"stats",
|
|
36
36
|
"analysis",
|
|
37
37
|
"funnels"
|
|
38
|
-
]
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./lib/index.d.ts",
|
|
42
|
+
"edge": {
|
|
43
|
+
"import": "./lib/edge/index.esm.js",
|
|
44
|
+
"require": "./lib/edge/index.cjs.js",
|
|
45
|
+
"default": "./lib/edge/index.js"
|
|
46
|
+
},
|
|
47
|
+
"node": {
|
|
48
|
+
"import": "./lib/node/index.esm.js",
|
|
49
|
+
"require": "./lib/node/index.cjs.js",
|
|
50
|
+
"default": "./lib/node/index.js"
|
|
51
|
+
},
|
|
52
|
+
"edge-light": {
|
|
53
|
+
"import": "./lib/edge/index.esm.js",
|
|
54
|
+
"require": "./lib/edge/index.cjs.js",
|
|
55
|
+
"default": "./lib/edge/index.js"
|
|
56
|
+
},
|
|
57
|
+
"workerd": {
|
|
58
|
+
"import": "./lib/edge/index.esm.js",
|
|
59
|
+
"require": "./lib/edge/index.cjs.js",
|
|
60
|
+
"default": "./lib/edge/index.js"
|
|
61
|
+
},
|
|
62
|
+
"import": "./lib/node/index.esm.js",
|
|
63
|
+
"require": "./lib/node/index.cjs.js"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
39
66
|
}
|
|
@@ -2,43 +2,29 @@ import { version } from '../package.json'
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
JsonType,
|
|
5
|
-
PostHogCoreOptions,
|
|
6
5
|
PostHogCoreStateless,
|
|
7
6
|
PostHogDecideResponse,
|
|
8
7
|
PostHogFetchOptions,
|
|
9
8
|
PostHogFetchResponse,
|
|
10
9
|
PostHogFlagsAndPayloadsResponse,
|
|
11
10
|
PostHogPersistedProperty,
|
|
12
|
-
} from '
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { FeatureFlagsPoller } from './feature-flags'
|
|
11
|
+
} from 'posthog-core'
|
|
12
|
+
import { EventMessage, GroupIdentifyMessage, IdentifyMessage, PostHogNodeV1, PostHogOptions } from './types'
|
|
13
|
+
import { FeatureFlagDetail, FeatureFlagValue } from 'posthog-core'
|
|
14
|
+
import { FeatureFlagsPoller } from './extensions/feature-flags/feature-flags'
|
|
17
15
|
import fetch from './fetch'
|
|
18
|
-
import ErrorTracking from './error-tracking'
|
|
19
|
-
import { getFeatureFlagValue } from 'posthog-core
|
|
20
|
-
|
|
21
|
-
export type PostHogOptions = PostHogCoreOptions & {
|
|
22
|
-
persistence?: 'memory'
|
|
23
|
-
personalApiKey?: string
|
|
24
|
-
privacyMode?: boolean
|
|
25
|
-
enableExceptionAutocapture?: boolean
|
|
26
|
-
// The interval in milliseconds between polls for refreshing feature flag definitions. Defaults to 30 seconds.
|
|
27
|
-
featureFlagsPollingInterval?: number
|
|
28
|
-
// Maximum size of cache that deduplicates $feature_flag_called calls per user.
|
|
29
|
-
maxCacheSize?: number
|
|
30
|
-
fetch?: (url: string, options: PostHogFetchOptions) => Promise<PostHogFetchResponse>
|
|
31
|
-
}
|
|
16
|
+
import ErrorTracking from './extensions/error-tracking'
|
|
17
|
+
import { getFeatureFlagValue } from 'posthog-core'
|
|
18
|
+
import { PostHogMemoryStorage } from './storage-memory'
|
|
32
19
|
|
|
33
20
|
// Standard local evaluation rate limit is 600 per minute (10 per second),
|
|
34
21
|
// so the fastest a poller should ever be set is 100ms.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export const SIXTY_SECONDS = 60 * 1000
|
|
22
|
+
const MINIMUM_POLLING_INTERVAL = 100
|
|
23
|
+
const THIRTY_SECONDS = 30 * 1000
|
|
38
24
|
const MAX_CACHE_SIZE = 50 * 1000
|
|
39
25
|
|
|
40
26
|
// The actual exported Nodejs API.
|
|
41
|
-
export class
|
|
27
|
+
export abstract class PostHogBackendClient extends PostHogCoreStateless implements PostHogNodeV1 {
|
|
42
28
|
private _memoryStorage = new PostHogMemoryStorage()
|
|
43
29
|
|
|
44
30
|
private featureFlagsPoller?: FeatureFlagsPoller
|
|
@@ -97,10 +83,6 @@ export class PostHog extends PostHogCoreStateless implements PostHogNodeV1 {
|
|
|
97
83
|
fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
|
|
98
84
|
return this.options.fetch ? this.options.fetch(url, options) : fetch(url, options)
|
|
99
85
|
}
|
|
100
|
-
|
|
101
|
-
getLibraryId(): string {
|
|
102
|
-
return 'posthog-node'
|
|
103
|
-
}
|
|
104
86
|
getLibraryVersion(): string {
|
|
105
87
|
return version
|
|
106
88
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from '../exports'
|
|
2
|
+
|
|
3
|
+
import ErrorTracking from '../extensions/error-tracking'
|
|
4
|
+
|
|
5
|
+
import { PostHogBackendClient } from '../client'
|
|
6
|
+
import { createStackParser } from '../extensions/error-tracking/stack-parser'
|
|
7
|
+
|
|
8
|
+
ErrorTracking.stackParser = createStackParser()
|
|
9
|
+
ErrorTracking.frameModifiers = []
|
|
10
|
+
|
|
11
|
+
export class PostHog extends PostHogBackendClient {
|
|
12
|
+
getLibraryId(): string {
|
|
13
|
+
return 'posthog-edge'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from '../exports'
|
|
2
|
+
|
|
3
|
+
import { createGetModuleFromFilename } from '../extensions/error-tracking/get-module.node'
|
|
4
|
+
import { addSourceContext } from '../extensions/error-tracking/context-lines.node'
|
|
5
|
+
import ErrorTracking from '../extensions/error-tracking'
|
|
6
|
+
|
|
7
|
+
import { PostHogBackendClient } from '../client'
|
|
8
|
+
import { createStackParser } from '../extensions/error-tracking/stack-parser'
|
|
9
|
+
|
|
10
|
+
ErrorTracking.stackParser = createStackParser(createGetModuleFromFilename())
|
|
11
|
+
ErrorTracking.frameModifiers = [addSourceContext]
|
|
12
|
+
|
|
13
|
+
export class PostHog extends PostHogBackendClient {
|
|
14
|
+
getLibraryId(): string {
|
|
15
|
+
return 'posthog-node'
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/exports.ts
ADDED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
2
2
|
// Licensed under the MIT License
|
|
3
3
|
|
|
4
|
-
import { EventHint } from '
|
|
4
|
+
import { EventHint } from './types'
|
|
5
5
|
|
|
6
6
|
type ErrorHandler = { _posthogErrorHandler: boolean } & ((error: Error) => void)
|
|
7
7
|
|
|
@@ -3,31 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { StackFrame } from './types'
|
|
5
5
|
import { ReduceableCache } from './reduceable-cache'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const nodeFs = new Lazy(async () => {
|
|
9
|
-
try {
|
|
10
|
-
return await import('fs')
|
|
11
|
-
} catch {
|
|
12
|
-
return undefined
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
export async function getNodeFs(): Promise<typeof import('fs') | undefined> {
|
|
17
|
-
return await nodeFs.getValue()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const nodeReadline = new Lazy(async () => {
|
|
21
|
-
try {
|
|
22
|
-
return await import('readline')
|
|
23
|
-
} catch {
|
|
24
|
-
return undefined
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export async function getNodeReadline(): Promise<typeof import('readline') | undefined> {
|
|
29
|
-
return await nodeReadline.getValue()
|
|
30
|
-
}
|
|
6
|
+
import { createReadStream } from 'node:fs'
|
|
7
|
+
import { createInterface } from 'node:readline'
|
|
31
8
|
|
|
32
9
|
const LRU_FILE_CONTENTS_CACHE = new ReduceableCache<string, Record<number, string>>(25)
|
|
33
10
|
const LRU_FILE_CONTENTS_FS_READ_FAILED = new ReduceableCache<string, 1>(20)
|
|
@@ -118,87 +95,77 @@ export async function addSourceContext(frames: StackFrame[]): Promise<StackFrame
|
|
|
118
95
|
*/
|
|
119
96
|
function getContextLinesFromFile(path: string, ranges: ReadlineRange[], output: Record<number, string>): Promise<void> {
|
|
120
97
|
return new Promise((resolve) => {
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
98
|
+
// It is important *not* to have any async code between createInterface and the 'line' event listener
|
|
99
|
+
// as it will cause the 'line' event to
|
|
100
|
+
// be emitted before the listener is attached.
|
|
101
|
+
const stream = createReadStream(path)
|
|
102
|
+
const lineReaded = createInterface({
|
|
103
|
+
input: stream,
|
|
104
|
+
})
|
|
129
105
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
106
|
+
// We need to explicitly destroy the stream to prevent memory leaks,
|
|
107
|
+
// removing the listeners on the readline interface is not enough.
|
|
108
|
+
// See: https://github.com/nodejs/node/issues/9002 and https://github.com/getsentry/sentry-javascript/issues/14892
|
|
109
|
+
function destroyStreamAndResolve(): void {
|
|
110
|
+
stream.destroy()
|
|
111
|
+
resolve()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Init at zero and increment at the start of the loop because lines are 1 indexed.
|
|
115
|
+
let lineNumber = 0
|
|
116
|
+
let currentRangeIndex = 0
|
|
117
|
+
const range = ranges[currentRangeIndex]
|
|
118
|
+
if (range === undefined) {
|
|
119
|
+
// We should never reach this point, but if we do, we should resolve the promise to prevent it from hanging.
|
|
120
|
+
destroyStreamAndResolve()
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
let rangeStart = range[0]
|
|
124
|
+
let rangeEnd = range[1]
|
|
125
|
+
|
|
126
|
+
// We use this inside Promise.all, so we need to resolve the promise even if there is an error
|
|
127
|
+
// to prevent Promise.all from short circuiting the rest.
|
|
128
|
+
function onStreamError(): void {
|
|
129
|
+
// Mark file path as failed to read and prevent multiple read attempts.
|
|
130
|
+
LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1)
|
|
131
|
+
lineReaded.close()
|
|
132
|
+
lineReaded.removeAllListeners()
|
|
133
|
+
destroyStreamAndResolve()
|
|
134
|
+
}
|
|
145
135
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
// We need to handle the error event to prevent the process from crashing in < Node 16
|
|
137
|
+
// https://github.com/nodejs/node/pull/31603
|
|
138
|
+
stream.on('error', onStreamError)
|
|
139
|
+
lineReaded.on('error', onStreamError)
|
|
140
|
+
lineReaded.on('close', destroyStreamAndResolve)
|
|
141
|
+
|
|
142
|
+
lineReaded.on('line', (line) => {
|
|
143
|
+
lineNumber++
|
|
144
|
+
if (lineNumber < rangeStart) {
|
|
153
145
|
return
|
|
154
146
|
}
|
|
155
|
-
let rangeStart = range[0]
|
|
156
|
-
let rangeEnd = range[1]
|
|
157
|
-
|
|
158
|
-
// We use this inside Promise.all, so we need to resolve the promise even if there is an error
|
|
159
|
-
// to prevent Promise.all from short circuiting the rest.
|
|
160
|
-
function onStreamError(): void {
|
|
161
|
-
// Mark file path as failed to read and prevent multiple read attempts.
|
|
162
|
-
LRU_FILE_CONTENTS_FS_READ_FAILED.set(path, 1)
|
|
163
|
-
lineReaded.close()
|
|
164
|
-
lineReaded.removeAllListeners()
|
|
165
|
-
destroyStreamAndResolve()
|
|
166
|
-
}
|
|
167
147
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
stream.on('error', onStreamError)
|
|
171
|
-
lineReaded.on('error', onStreamError)
|
|
172
|
-
lineReaded.on('close', destroyStreamAndResolve)
|
|
148
|
+
// !Warning: This mutates the cache by storing the snipped line into the cache.
|
|
149
|
+
output[lineNumber] = snipLine(line, 0)
|
|
173
150
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
151
|
+
if (lineNumber >= rangeEnd) {
|
|
152
|
+
if (currentRangeIndex === ranges.length - 1) {
|
|
153
|
+
// We need to close the file stream and remove listeners, else the reader will continue to run our listener;
|
|
154
|
+
lineReaded.close()
|
|
155
|
+
lineReaded.removeAllListeners()
|
|
177
156
|
return
|
|
178
157
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
lineReaded.close()
|
|
187
|
-
lineReaded.removeAllListeners()
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
currentRangeIndex++
|
|
191
|
-
const range = ranges[currentRangeIndex]
|
|
192
|
-
if (range === undefined) {
|
|
193
|
-
// This should never happen as it means we have a bug in the context.
|
|
194
|
-
lineReaded.close()
|
|
195
|
-
lineReaded.removeAllListeners()
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
rangeStart = range[0]
|
|
199
|
-
rangeEnd = range[1]
|
|
158
|
+
currentRangeIndex++
|
|
159
|
+
const range = ranges[currentRangeIndex]
|
|
160
|
+
if (range === undefined) {
|
|
161
|
+
// This should never happen as it means we have a bug in the context.
|
|
162
|
+
lineReaded.close()
|
|
163
|
+
lineReaded.removeAllListeners()
|
|
164
|
+
return
|
|
200
165
|
}
|
|
201
|
-
|
|
166
|
+
rangeStart = range[0]
|
|
167
|
+
rangeEnd = range[1]
|
|
168
|
+
}
|
|
202
169
|
})
|
|
203
170
|
})
|
|
204
171
|
}
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
// Licensed under the MIT License
|
|
3
3
|
|
|
4
4
|
import { isError, isErrorEvent, isEvent, isPlainObject } from './type-checking'
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
ErrorProperties,
|
|
7
|
+
EventHint,
|
|
8
|
+
Exception,
|
|
9
|
+
Mechanism,
|
|
10
|
+
StackFrame,
|
|
11
|
+
StackFrameModifierFn,
|
|
12
|
+
StackParser,
|
|
13
|
+
} from './types'
|
|
7
14
|
|
|
8
15
|
export async function propertiesFromUnknownInput(
|
|
9
16
|
stackParser: StackParser,
|
|
17
|
+
frameModifiers: StackFrameModifierFn[],
|
|
10
18
|
input: unknown,
|
|
11
19
|
hint?: EventHint
|
|
12
20
|
): Promise<ErrorProperties> {
|
|
@@ -19,7 +27,7 @@ export async function propertiesFromUnknownInput(
|
|
|
19
27
|
const errorList = getErrorList(mechanism, input, hint)
|
|
20
28
|
const exceptionList = await Promise.all(
|
|
21
29
|
errorList.map(async (error) => {
|
|
22
|
-
const exception = await exceptionFromError(stackParser, error)
|
|
30
|
+
const exception = await exceptionFromError(stackParser, frameModifiers, error)
|
|
23
31
|
exception.value = exception.value || ''
|
|
24
32
|
exception.type = exception.type || 'Error'
|
|
25
33
|
exception.mechanism = mechanism
|
|
@@ -240,13 +248,22 @@ function serializeEventTarget(target: unknown): string {
|
|
|
240
248
|
/**
|
|
241
249
|
* Extracts stack frames from the error and builds an Exception
|
|
242
250
|
*/
|
|
243
|
-
async function exceptionFromError(
|
|
251
|
+
async function exceptionFromError(
|
|
252
|
+
stackParser: StackParser,
|
|
253
|
+
frameModifiers: StackFrameModifierFn[],
|
|
254
|
+
error: Error
|
|
255
|
+
): Promise<Exception> {
|
|
244
256
|
const exception: Exception = {
|
|
245
257
|
type: error.name || error.constructor.name,
|
|
246
258
|
value: error.message,
|
|
247
259
|
}
|
|
248
260
|
|
|
249
|
-
|
|
261
|
+
let frames = parseStackFrames(stackParser, error)
|
|
262
|
+
|
|
263
|
+
for (const modifier of frameModifiers) {
|
|
264
|
+
frames = await modifier(frames)
|
|
265
|
+
}
|
|
266
|
+
|
|
250
267
|
if (frames.length) {
|
|
251
268
|
exception.stacktrace = { frames, type: 'raw' }
|
|
252
269
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
2
|
+
// Licensed under the MIT License
|
|
3
|
+
|
|
4
|
+
import { posix, sep, dirname } from 'path'
|
|
5
|
+
|
|
6
|
+
/** Creates a function that gets the module name from a filename */
|
|
7
|
+
export function createGetModuleFromFilename(
|
|
8
|
+
basePath: string = process.argv[1] ? dirname(process.argv[1]) : process.cwd(),
|
|
9
|
+
isWindows: boolean = sep === '\\'
|
|
10
|
+
): (filename: string | undefined) => string | undefined {
|
|
11
|
+
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath
|
|
12
|
+
|
|
13
|
+
return (filename: string | undefined) => {
|
|
14
|
+
if (!filename) {
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line prefer-const
|
|
21
|
+
let { dir, base: file, ext } = posix.parse(normalizedFilename)
|
|
22
|
+
|
|
23
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
24
|
+
file = file.slice(0, ext.length * -1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The file name might be URI-encoded which we want to decode to
|
|
28
|
+
// the original file name.
|
|
29
|
+
const decodedFile = decodeURIComponent(file)
|
|
30
|
+
|
|
31
|
+
if (!dir) {
|
|
32
|
+
// No dirname whatsoever
|
|
33
|
+
dir = '.'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const n = dir.lastIndexOf('/node_modules')
|
|
37
|
+
if (n > -1) {
|
|
38
|
+
return `${dir.slice(n + 14).replace(/\//g, '.')}:${decodedFile}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Let's see if it's a part of the main module
|
|
42
|
+
// To be a part of main module, it has to share the same base
|
|
43
|
+
if (dir.startsWith(normalizedBase)) {
|
|
44
|
+
const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.')
|
|
45
|
+
return moduleName ? `${moduleName}:${decodedFile}` : decodedFile
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return decodedFile
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** normalizes Windows paths */
|
|
53
|
+
function normalizeWindowsPath(path: string): string {
|
|
54
|
+
return path
|
|
55
|
+
.replace(/^[A-Z]:/, '') // remove Windows-style prefix
|
|
56
|
+
.replace(/\\/g, '/') // replace all `\` instances with `/`
|
|
57
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { EventHint } from './
|
|
2
|
-
import { addUncaughtExceptionListener, addUnhandledRejectionListener } from './
|
|
3
|
-
import {
|
|
1
|
+
import { EventHint, StackFrameModifierFn, StackParser } from './types'
|
|
2
|
+
import { addUncaughtExceptionListener, addUnhandledRejectionListener } from './autocapture'
|
|
3
|
+
import { PostHogBackendClient } from '../../client'
|
|
4
4
|
import { uuidv7 } from 'posthog-core/src/vendor/uuidv7'
|
|
5
|
-
import { propertiesFromUnknownInput } from './
|
|
6
|
-
import { EventMessage } from '
|
|
7
|
-
import { defaultStackParser } from './extensions/error-tracking/stack-trace'
|
|
5
|
+
import { propertiesFromUnknownInput } from './error-conversion'
|
|
6
|
+
import { EventMessage, PostHogOptions } from '../../types'
|
|
8
7
|
|
|
9
8
|
const SHUTDOWN_TIMEOUT = 2000
|
|
10
9
|
|
|
11
10
|
export default class ErrorTracking {
|
|
12
|
-
private client:
|
|
11
|
+
private client: PostHogBackendClient
|
|
13
12
|
private _exceptionAutocaptureEnabled: boolean
|
|
14
13
|
|
|
14
|
+
static stackParser: StackParser
|
|
15
|
+
static frameModifiers: StackFrameModifierFn[]
|
|
16
|
+
|
|
15
17
|
static async captureException(
|
|
16
|
-
client:
|
|
18
|
+
client: PostHogBackendClient,
|
|
17
19
|
error: unknown,
|
|
18
20
|
hint: EventHint,
|
|
19
21
|
distinctId?: string,
|
|
@@ -27,7 +29,7 @@ export default class ErrorTracking {
|
|
|
27
29
|
properties.$process_person_profile = false
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
const exceptionProperties = await propertiesFromUnknownInput(
|
|
32
|
+
const exceptionProperties = await propertiesFromUnknownInput(this.stackParser, this.frameModifiers, error, hint)
|
|
31
33
|
|
|
32
34
|
client.capture({
|
|
33
35
|
event: '$exception',
|
|
@@ -39,7 +41,7 @@ export default class ErrorTracking {
|
|
|
39
41
|
})
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
constructor(client:
|
|
44
|
+
constructor(client: PostHogBackendClient, options: PostHogOptions) {
|
|
43
45
|
this.client = client
|
|
44
46
|
this._exceptionAutocaptureEnabled = options.enableExceptionAutocapture || false
|
|
45
47
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
2
2
|
// Licensed under the MIT License
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { StackFrame, StackLineParser, StackLineParserFn, StackParser } from './types'
|
|
6
|
-
|
|
7
|
-
type GetModuleFn = (filename: string | undefined) => string | undefined
|
|
4
|
+
import { GetModuleFn, StackFrame, StackLineParser, StackLineParserFn, StackParser } from './types'
|
|
8
5
|
|
|
9
6
|
// This was originally forked from https://github.com/csnover/TraceKit, and was largely
|
|
10
7
|
// re-written as part of raven - js.
|
|
@@ -37,7 +34,7 @@ const STACKTRACE_FRAME_LIMIT = 50
|
|
|
37
34
|
const UNKNOWN_FUNCTION = '?'
|
|
38
35
|
|
|
39
36
|
/** Node Stack line parser */
|
|
40
|
-
|
|
37
|
+
function node(getModule?: GetModuleFn): StackLineParserFn {
|
|
41
38
|
const FILENAME_MATCH = /^\s*[-]{4,}$/
|
|
42
39
|
const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/
|
|
43
40
|
|
|
@@ -123,7 +120,7 @@ export function node(getModule?: GetModuleFn): StackLineParserFn {
|
|
|
123
120
|
/**
|
|
124
121
|
* Does this filename look like it's part of the app code?
|
|
125
122
|
*/
|
|
126
|
-
|
|
123
|
+
function filenameIsInApp(filename: string, isNative: boolean = false): boolean {
|
|
127
124
|
const isInternal =
|
|
128
125
|
isNative ||
|
|
129
126
|
(filename &&
|
|
@@ -147,66 +144,12 @@ function _parseIntOrUndefined(input: string | undefined): number | undefined {
|
|
|
147
144
|
return parseInt(input || '', 10) || undefined
|
|
148
145
|
}
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser {
|
|
151
148
|
return [90, node(getModule)]
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
export
|
|
155
|
-
|
|
156
|
-
/** Creates a function that gets the module name from a filename */
|
|
157
|
-
export function createGetModuleFromFilename(
|
|
158
|
-
basePath: string = process.argv[1] ? dirname(process.argv[1]) : process.cwd(),
|
|
159
|
-
isWindows: boolean = sep === '\\'
|
|
160
|
-
): (filename: string | undefined) => string | undefined {
|
|
161
|
-
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath
|
|
162
|
-
|
|
163
|
-
return (filename: string | undefined) => {
|
|
164
|
-
if (!filename) {
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename
|
|
169
|
-
|
|
170
|
-
// eslint-disable-next-line prefer-const
|
|
171
|
-
let { dir, base: file, ext } = posix.parse(normalizedFilename)
|
|
172
|
-
|
|
173
|
-
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
174
|
-
file = file.slice(0, ext.length * -1)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// The file name might be URI-encoded which we want to decode to
|
|
178
|
-
// the original file name.
|
|
179
|
-
const decodedFile = decodeURIComponent(file)
|
|
180
|
-
|
|
181
|
-
if (!dir) {
|
|
182
|
-
// No dirname whatsoever
|
|
183
|
-
dir = '.'
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const n = dir.lastIndexOf('/node_modules')
|
|
187
|
-
if (n > -1) {
|
|
188
|
-
return `${dir.slice(n + 14).replace(/\//g, '.')}:${decodedFile}`
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Let's see if it's a part of the main module
|
|
192
|
-
// To be a part of main module, it has to share the same base
|
|
193
|
-
if (dir.startsWith(normalizedBase)) {
|
|
194
|
-
const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.')
|
|
195
|
-
return moduleName ? `${moduleName}:${decodedFile}` : decodedFile
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return decodedFile
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** normalizes Windows paths */
|
|
203
|
-
function normalizeWindowsPath(path: string): string {
|
|
204
|
-
return path
|
|
205
|
-
.replace(/^[A-Z]:/, '') // remove Windows-style prefix
|
|
206
|
-
.replace(/\\/g, '/') // replace all `\` instances with `/`
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function createStackParser(...parsers: StackLineParser[]): StackParser {
|
|
151
|
+
export function createStackParser(getModule?: GetModuleFn): StackParser {
|
|
152
|
+
const parsers = [nodeStackLineParser(getModule)]
|
|
210
153
|
const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map((p) => p[1])
|
|
211
154
|
|
|
212
155
|
return (stack: string, skipFirstLines: number = 0): StackFrame[] => {
|
|
@@ -248,7 +191,7 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser {
|
|
|
248
191
|
}
|
|
249
192
|
}
|
|
250
193
|
|
|
251
|
-
|
|
194
|
+
function reverseAndStripFrames(stack: ReadonlyArray<StackFrame>): StackFrame[] {
|
|
252
195
|
if (!stack.length) {
|
|
253
196
|
return []
|
|
254
197
|
}
|
|
@@ -42,10 +42,14 @@ export interface Mechanism {
|
|
|
42
42
|
synthetic?: boolean
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export type GetModuleFn = (filename: string | undefined) => string | undefined
|
|
46
|
+
|
|
45
47
|
export type StackParser = (stack: string, skipFirstLines?: number) => StackFrame[]
|
|
46
48
|
export type StackLineParserFn = (line: string) => StackFrame | undefined
|
|
47
49
|
export type StackLineParser = [number, StackLineParserFn]
|
|
48
50
|
|
|
51
|
+
export type StackFrameModifierFn = (frames: StackFrame[]) => Promise<StackFrame[]>
|
|
52
|
+
|
|
49
53
|
export interface StackFrame {
|
|
50
54
|
platform: string
|
|
51
55
|
filename?: string
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as http from 'node:http'
|
|
2
2
|
import { uuidv7 } from 'posthog-core/src/vendor/uuidv7'
|
|
3
|
-
import ErrorTracking from '
|
|
4
|
-
import {
|
|
3
|
+
import ErrorTracking from './error-tracking'
|
|
4
|
+
import { PostHogBackendClient } from '../client'
|
|
5
5
|
|
|
6
6
|
type ExpressMiddleware = (req: http.IncomingMessage, res: http.ServerResponse, next: () => void) => void
|
|
7
7
|
|
|
@@ -22,7 +22,7 @@ interface MiddlewareError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function setupExpressErrorHandler(
|
|
25
|
-
_posthog:
|
|
25
|
+
_posthog: PostHogBackendClient,
|
|
26
26
|
app: {
|
|
27
27
|
use: (middleware: ExpressMiddleware | ExpressErrorMiddleware) => unknown
|
|
28
28
|
}
|