@multiplayer-app/session-recorder-node 1.2.1 → 1.2.3
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/LICENSE +0 -1
- package/README.md +194 -60
- package/dist/sessionRecorder.d.ts.map +1 -1
- package/dist/sessionRecorder.js +3 -0
- package/dist/sessionRecorder.js.map +1 -1
- package/docs/img/header-js.png +0 -0
- package/examples/cli/package-lock.json +4260 -0
- package/examples/cli/package.json +56 -0
- package/examples/cli/src/config.ts +47 -0
- package/examples/cli/src/index.ts +43 -0
- package/examples/cli/src/opentelemetry.ts +140 -0
- package/examples/cli/tsconfig.json +36 -0
- package/examples/http-server/package-lock.json +5411 -0
- package/examples/http-server/package.json +48 -0
- package/examples/http-server/src/api.ts +20 -0
- package/examples/http-server/src/config.ts +24 -0
- package/examples/http-server/src/index.ts +46 -0
- package/examples/http-server/src/opentelemetry.ts +140 -0
- package/examples/http-server/tsconfig.json +36 -0
- package/package.json +2 -2
- package/src/sessionRecorder.ts +4 -0
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "http-server-exmaple",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18",
|
|
8
|
+
"npm": ">=8"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "nodemon --watch \"src/**\" --ext \"ts,json\" --ignore \"src/**/*.spec.ts\" --exec \"ts-node src/index.ts\"",
|
|
13
|
+
"build": "npm run clean && npm run compile",
|
|
14
|
+
"clean": "rm -rf ./dist",
|
|
15
|
+
"compile": "tsc --build --force",
|
|
16
|
+
"lint": "eslint src/**/*.ts --config ../../.eslintrc"
|
|
17
|
+
},
|
|
18
|
+
"author": "Multiplayer",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@multiplayer-app/session-recorder-node": "1.2.2",
|
|
21
|
+
"@opentelemetry/api": "1.9.0",
|
|
22
|
+
"@opentelemetry/api-logs": "0.203.0",
|
|
23
|
+
"@opentelemetry/auto-instrumentations-node": "0.62.1",
|
|
24
|
+
"@opentelemetry/core": "2.0.1",
|
|
25
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.203.0",
|
|
26
|
+
"@opentelemetry/exporter-logs-otlp-http": "0.203.0",
|
|
27
|
+
"@opentelemetry/resources": "2.0.1",
|
|
28
|
+
"@opentelemetry/sdk-logs": "0.203.0",
|
|
29
|
+
"@opentelemetry/sdk-trace-base": "2.0.1",
|
|
30
|
+
"@opentelemetry/sdk-trace-node": "2.0.1",
|
|
31
|
+
"@opentelemetry/semantic-conventions": "1.36.0",
|
|
32
|
+
"body-parser": "1.20.2",
|
|
33
|
+
"cors": "2.8.5",
|
|
34
|
+
"dotenv": "16.3.1",
|
|
35
|
+
"express": "4.18.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/body-parser": "1.19.2",
|
|
39
|
+
"@types/cors": "2.8.13",
|
|
40
|
+
"@types/express": "4.17.17",
|
|
41
|
+
"@types/jest": "29.5.4",
|
|
42
|
+
"@types/node": "20.5.7",
|
|
43
|
+
"eslint": "8.48.0",
|
|
44
|
+
"nodemon": "3.1.9",
|
|
45
|
+
"ts-node": "10.9.1",
|
|
46
|
+
"typescript": "5.7.3"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import express, {
|
|
2
|
+
type Request,
|
|
3
|
+
type Response,
|
|
4
|
+
type NextFunction
|
|
5
|
+
} from 'express'
|
|
6
|
+
|
|
7
|
+
const { Router } = express
|
|
8
|
+
const router = Router()
|
|
9
|
+
|
|
10
|
+
const health = async (req: Request, res: Response, next: NextFunction) => {
|
|
11
|
+
try {
|
|
12
|
+
return res.status(200).json({})
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return next(err)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
router.route('/').get(health)
|
|
19
|
+
|
|
20
|
+
export default router
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const NODE_ENV = process.env.NODE_ENV || 'development'
|
|
2
|
+
export const isProduction = NODE_ENV === 'production'
|
|
3
|
+
|
|
4
|
+
export const PORT = Number(process.env.PORT || 3000)
|
|
5
|
+
export const API_PREFIX = process.env.API_PREFIX || '/v1/api'
|
|
6
|
+
|
|
7
|
+
export const LOG_LEVEL = process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug')
|
|
8
|
+
|
|
9
|
+
export const COMPONENT_NAME = process.env.npm_package_name?.split('/').pop() as string || process.env.SERVICE_NAME || 'timegate'
|
|
10
|
+
export const COMPONENT_VERSION = process.env.npm_package_version || '0.0.1'
|
|
11
|
+
export const ENVIRONMENT = process.env.ENVIRONMENT || 'staging'
|
|
12
|
+
|
|
13
|
+
export const MULTIPLAYER_OTLP_KEY = process.env.MULTIPLAYER_OTLP_KEY as string
|
|
14
|
+
|
|
15
|
+
if (!MULTIPLAYER_OTLP_KEY) {
|
|
16
|
+
throw new Error('MULTIPLAYER_OTLP_KEY is not set')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const OTLP_TRACES_ENDPOINT = process.env.OTLP_TRACES_ENDPOINT || 'https://api.multiplayer.app/v1/traces'
|
|
20
|
+
export const OTLP_LOGS_ENDPOINT = process.env.OTLP_LOGS_ENDPOINT || 'https://api.multiplayer.app/v1/logs'
|
|
21
|
+
|
|
22
|
+
export const MULTIPLAYER_OTLP_SPAN_RATIO = process.env.MULTIPLAYER_OTLP_SPAN_RATIO
|
|
23
|
+
? Number(process.env.MULTIPLAYER_OTLP_SPAN_RATIO)
|
|
24
|
+
: 0.01
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
import './opentelemetry'
|
|
3
|
+
import http from 'http'
|
|
4
|
+
import cors from 'cors'
|
|
5
|
+
import bodyParser from 'body-parser'
|
|
6
|
+
import express, {
|
|
7
|
+
type Request,
|
|
8
|
+
type Response,
|
|
9
|
+
type NextFunction,
|
|
10
|
+
} from 'express'
|
|
11
|
+
import { PORT, API_PREFIX } from './config'
|
|
12
|
+
import api from './api'
|
|
13
|
+
|
|
14
|
+
const app = express()
|
|
15
|
+
|
|
16
|
+
app.use(cors())
|
|
17
|
+
app.use(bodyParser.urlencoded({ extended: true }))
|
|
18
|
+
app.use(bodyParser.json())
|
|
19
|
+
|
|
20
|
+
app.use(API_PREFIX, api)
|
|
21
|
+
|
|
22
|
+
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
23
|
+
res.status(404).send('Not found')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const httpServer = http.createServer(app)
|
|
27
|
+
const onReady = () => {
|
|
28
|
+
console.log(`🚀 Server ready at http://localhost:${PORT}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
httpServer.listen(PORT, onReady)
|
|
32
|
+
|
|
33
|
+
const exitHandler = async (error: Error) => {
|
|
34
|
+
if (error) {
|
|
35
|
+
console.log('Server exited with error', error)
|
|
36
|
+
}
|
|
37
|
+
process.removeListener('exit', exitHandler)
|
|
38
|
+
process.exit()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
process.on('exit', exitHandler)
|
|
42
|
+
process.on('SIGINT', exitHandler)
|
|
43
|
+
process.on('SIGTERM', exitHandler)
|
|
44
|
+
process.on('uncaughtException', (err) => {
|
|
45
|
+
console.error('uncaughtException', err)
|
|
46
|
+
})
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { hostname } from 'os'
|
|
2
|
+
import { node, NodeSDK } from '@opentelemetry/sdk-node'
|
|
3
|
+
import {
|
|
4
|
+
BatchSpanProcessor,
|
|
5
|
+
ParentBasedSampler,
|
|
6
|
+
} from '@opentelemetry/sdk-trace-base'
|
|
7
|
+
import {
|
|
8
|
+
getNodeAutoInstrumentations,
|
|
9
|
+
getResourceDetectors,
|
|
10
|
+
} from '@opentelemetry/auto-instrumentations-node'
|
|
11
|
+
import {
|
|
12
|
+
resourceFromAttributes,
|
|
13
|
+
detectResources,
|
|
14
|
+
} from '@opentelemetry/resources'
|
|
15
|
+
import {
|
|
16
|
+
ATTR_SERVICE_NAME,
|
|
17
|
+
ATTR_SERVICE_VERSION,
|
|
18
|
+
SEMRESATTRS_HOST_NAME,
|
|
19
|
+
SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,
|
|
20
|
+
SEMRESATTRS_PROCESS_RUNTIME_VERSION,
|
|
21
|
+
SEMRESATTRS_PROCESS_PID,
|
|
22
|
+
} from '@opentelemetry/semantic-conventions'
|
|
23
|
+
import api from '@opentelemetry/api'
|
|
24
|
+
// import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
|
|
25
|
+
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
|
26
|
+
import {
|
|
27
|
+
SessionRecorderHttpInstrumentationHooksNode,
|
|
28
|
+
SessionRecorderTraceIdRatioBasedSampler,
|
|
29
|
+
SessionRecorderIdGenerator,
|
|
30
|
+
SessionRecorderHttpTraceExporter,
|
|
31
|
+
SessionRecorderHttpLogsExporter,
|
|
32
|
+
} from '@multiplayer-app/session-recorder-node'
|
|
33
|
+
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'
|
|
34
|
+
import * as apiLogs from '@opentelemetry/api-logs'
|
|
35
|
+
// import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
OTLP_TRACES_ENDPOINT,
|
|
39
|
+
OTLP_LOGS_ENDPOINT,
|
|
40
|
+
MULTIPLAYER_OTLP_KEY,
|
|
41
|
+
MULTIPLAYER_OTLP_SPAN_RATIO,
|
|
42
|
+
COMPONENT_NAME,
|
|
43
|
+
COMPONENT_VERSION,
|
|
44
|
+
ENVIRONMENT,
|
|
45
|
+
} from './config'
|
|
46
|
+
|
|
47
|
+
// NOTE: Update instrumentation configuration as needed
|
|
48
|
+
// For more see: https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node
|
|
49
|
+
const instrumentations = [
|
|
50
|
+
getNodeAutoInstrumentations({
|
|
51
|
+
'@opentelemetry/instrumentation-http': {
|
|
52
|
+
requestHook: SessionRecorderHttpInstrumentationHooksNode.requestHook({
|
|
53
|
+
maskHeadersList: ['X-Api-Key'],
|
|
54
|
+
maxPayloadSizeBytes: 5000,
|
|
55
|
+
schemifyDocSpanPayload: false,
|
|
56
|
+
isMaskBodyEnabled: false,
|
|
57
|
+
}),
|
|
58
|
+
responseHook: SessionRecorderHttpInstrumentationHooksNode.responseHook({
|
|
59
|
+
maskHeadersList: ['X-Api-Key'],
|
|
60
|
+
maxPayloadSizeBytes: 5000,
|
|
61
|
+
schemifyDocSpanPayload: false,
|
|
62
|
+
isMaskBodyEnabled: false,
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
const getResource = () => {
|
|
69
|
+
const resourceWithAttributes = resourceFromAttributes({
|
|
70
|
+
[ATTR_SERVICE_NAME]: COMPONENT_NAME,
|
|
71
|
+
[ATTR_SERVICE_VERSION]: COMPONENT_VERSION,
|
|
72
|
+
[SEMRESATTRS_HOST_NAME]: hostname(),
|
|
73
|
+
[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: ENVIRONMENT,
|
|
74
|
+
[SEMRESATTRS_PROCESS_RUNTIME_VERSION]: process.version,
|
|
75
|
+
[SEMRESATTRS_PROCESS_PID]: process.pid,
|
|
76
|
+
})
|
|
77
|
+
const detectedResources = detectResources({ detectors: getResourceDetectors() })
|
|
78
|
+
const resource = resourceWithAttributes.merge(detectedResources)
|
|
79
|
+
|
|
80
|
+
return resource
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const idGenerator = new SessionRecorderIdGenerator()
|
|
84
|
+
|
|
85
|
+
const opentelemetry = () => {
|
|
86
|
+
// const traceExporter = new OTLPTraceExporter({
|
|
87
|
+
// url: OTLP_TRACES_ENDPOINT,
|
|
88
|
+
// headers: {
|
|
89
|
+
// Authorization: MULTIPLAYER_OTLP_KEY
|
|
90
|
+
// },
|
|
91
|
+
// })
|
|
92
|
+
const traceExporter = new SessionRecorderHttpTraceExporter({
|
|
93
|
+
apiKey: MULTIPLAYER_OTLP_KEY,
|
|
94
|
+
url: OTLP_TRACES_ENDPOINT,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const resource = getResource()
|
|
98
|
+
|
|
99
|
+
const provider = new node.NodeTracerProvider({
|
|
100
|
+
resource,
|
|
101
|
+
spanProcessors: [
|
|
102
|
+
new BatchSpanProcessor(traceExporter),
|
|
103
|
+
],
|
|
104
|
+
sampler: new ParentBasedSampler({
|
|
105
|
+
root: new SessionRecorderTraceIdRatioBasedSampler(MULTIPLAYER_OTLP_SPAN_RATIO),
|
|
106
|
+
}),
|
|
107
|
+
idGenerator,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// const logExporter = new OTLPLogExporter({
|
|
111
|
+
// url: OTLP_LOGS_ENDPOINT,
|
|
112
|
+
// headers: {
|
|
113
|
+
// Authorization: MULTIPLAYER_OTLP_KEY
|
|
114
|
+
// },
|
|
115
|
+
// })
|
|
116
|
+
const logExporter = new SessionRecorderHttpLogsExporter({
|
|
117
|
+
apiKey: MULTIPLAYER_OTLP_KEY,
|
|
118
|
+
url: OTLP_LOGS_ENDPOINT,
|
|
119
|
+
})
|
|
120
|
+
const logRecordProcessor = new BatchLogRecordProcessor(logExporter)
|
|
121
|
+
|
|
122
|
+
const loggerProvider = new LoggerProvider({
|
|
123
|
+
resource,
|
|
124
|
+
processors: [logRecordProcessor],
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
apiLogs.logs.setGlobalLoggerProvider(loggerProvider)
|
|
128
|
+
|
|
129
|
+
provider.register()
|
|
130
|
+
api.trace.setGlobalTracerProvider(provider)
|
|
131
|
+
api.propagation.setGlobalPropagator(new W3CTraceContextPropagator())
|
|
132
|
+
|
|
133
|
+
const sdk = new NodeSDK({
|
|
134
|
+
instrumentations,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
sdk.start()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
opentelemetry()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"declaration": true,
|
|
4
|
+
"declarationMap": true,
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"module": "commonjs",
|
|
7
|
+
"noImplicitAny": false,
|
|
8
|
+
"noUnusedParameters": false,
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"noImplicitReturns": true,
|
|
16
|
+
"noImplicitThis": true,
|
|
17
|
+
"noUnusedLocals": false,
|
|
18
|
+
"preserveConstEnums": true,
|
|
19
|
+
"removeComments": false,
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"skipLibCheck": true,
|
|
22
|
+
"sourceMap": true,
|
|
23
|
+
"target": "ES2018",
|
|
24
|
+
"downlevelIteration": true,
|
|
25
|
+
"strict": true,
|
|
26
|
+
"composite": true,
|
|
27
|
+
"outDir": "./dist/",
|
|
28
|
+
"rootDir": "./src/",
|
|
29
|
+
"preserveSymlinks": true
|
|
30
|
+
},
|
|
31
|
+
"exclude": ["dist", "coverage", "jest.config.js", "__tests__", "migration"],
|
|
32
|
+
"references": [],
|
|
33
|
+
"ts-node": {
|
|
34
|
+
"files": true
|
|
35
|
+
}
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplayer-app/session-recorder-node",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Multiplayer Fullstack Session Recorder for Node.js",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Multiplayer Software, Inc.",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"prepublishOnly": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@multiplayer-app/session-recorder-common": "1.2.
|
|
32
|
+
"@multiplayer-app/session-recorder-common": "1.2.3",
|
|
33
33
|
"@opentelemetry/api": "^1.9.0",
|
|
34
34
|
"@opentelemetry/core": "^2.0.1",
|
|
35
35
|
"@opentelemetry/otlp-exporter-base": "^0.203.0",
|
package/src/sessionRecorder.ts
CHANGED
|
@@ -116,6 +116,10 @@ export class SessionRecorder {
|
|
|
116
116
|
session = await this._apiService.startSession(sessionPayload)
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
if (!session?.shortId) {
|
|
120
|
+
throw new Error('Failed to start session')
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
this._shortSessionId = session.shortId as string
|
|
120
124
|
|
|
121
125
|
(this._traceIdGenerator as SessionRecorderIdGenerator).setSessionId(
|
package/tsconfig.json
CHANGED