@stacknext/next 0.0.0-stage → 0.6.1
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 +21 -0
- package/README.md +35 -2
- package/dist/edge.d.ts +26 -0
- package/dist/edge.d.ts.map +1 -0
- package/dist/edge.js +29 -0
- package/dist/edge.js.map +1 -0
- package/dist/error.d.ts +36 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +143 -0
- package/dist/error.js.map +1 -0
- package/dist/exporter.d.ts +39 -0
- package/dist/exporter.d.ts.map +1 -0
- package/dist/exporter.js +170 -0
- package/dist/exporter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/manual.d.ts +31 -0
- package/dist/manual.d.ts.map +1 -0
- package/dist/manual.js +115 -0
- package/dist/manual.js.map +1 -0
- package/dist/register.d.ts +44 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +177 -0
- package/dist/register.js.map +1 -0
- package/package.json +63 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StackNext contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stacknext/next
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Next.js App Router instrumentation and OpenTelemetry exporter for
|
|
4
|
+
[StackNext](https://github.com/marrrkkk/stacknext).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install --save-dev @stacknext/cli @stacknext/next
|
|
8
|
+
npx stacknext init
|
|
9
|
+
npx stacknext dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`stacknext init` creates `instrumentation.ts` (or `instrumentation.js` for a
|
|
13
|
+
JavaScript project) only when none exists. Existing instrumentation is never
|
|
14
|
+
overwritten; call `registerStackNext()` from your own
|
|
15
|
+
`register()` hook instead. StackNext yields to an already-registered
|
|
16
|
+
OpenTelemetry provider rather than replacing it.
|
|
17
|
+
|
|
18
|
+
Optional helpers keep application work in the same trace:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import {
|
|
22
|
+
withStackNextSpan,
|
|
23
|
+
withStackNextSpanSync,
|
|
24
|
+
withStackNextAction,
|
|
25
|
+
} from '@stacknext/next'
|
|
26
|
+
|
|
27
|
+
const project = await withStackNextSpan('project.load', () => loadProject(id))
|
|
28
|
+
const digest = withStackNextSpanSync('auth.hash', () => hash(input))
|
|
29
|
+
const result = await withStackNextAction('action/save-project', () => save(form))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Helpers are no-ops without an active OpenTelemetry provider. The package is
|
|
33
|
+
ESM-only, supports Next.js 15 and 16 on the Node.js runtime, and requires
|
|
34
|
+
Node.js 20.9 or newer.
|
|
35
|
+
|
|
36
|
+
License: MIT.
|
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-safe entry point. Next.js compiles `instrumentation.ts` for the Edge
|
|
3
|
+
* runtime even when the project has no edge routes. The generated hook returns
|
|
4
|
+
* before importing StackNext when `NEXT_RUNTIME !== 'nodejs'`, but the bundler
|
|
5
|
+
* still resolves the dynamic import. This module keeps that resolution free of
|
|
6
|
+
* Node built-ins and OpenTelemetry Node SDK code.
|
|
7
|
+
*
|
|
8
|
+
* Instrumentation and the collector transport are intentionally inert here.
|
|
9
|
+
*/
|
|
10
|
+
import type { StackNextErrorInput } from '@stacknext/core';
|
|
11
|
+
import type { StackNextTransport } from '@stacknext/core/transport';
|
|
12
|
+
import type { NextRequestErrorContext, NextRequestInfo } from './error.js';
|
|
13
|
+
import type { StackNextRegisterOptions, StackNextRegistration } from './register.js';
|
|
14
|
+
export { StackNextSpanExporter, readableSpanToInput, spanAttributes, type StackNextSpanExporterOptions, } from './exporter.js';
|
|
15
|
+
export { withStackNextAction, withStackNextSpan, withStackNextSpanSync, type StackNextManualSpanOptions, } from './manual.js';
|
|
16
|
+
export type { NextRequestErrorContext, NextRequestInfo, } from './error.js';
|
|
17
|
+
export type { StackNextRegisterOptions, StackNextRegistration, } from './register.js';
|
|
18
|
+
export declare function registerStackNext(options?: StackNextRegisterOptions): Promise<StackNextRegistration | null>;
|
|
19
|
+
export declare const register: typeof registerStackNext;
|
|
20
|
+
export declare function getStackNextRegistration(): StackNextRegistration | undefined;
|
|
21
|
+
export declare function shutdownStackNext(): Promise<void>;
|
|
22
|
+
export declare function onRequestError(error: unknown, request: NextRequestInfo, context: NextRequestErrorContext): void;
|
|
23
|
+
type ErrorSink = Pick<StackNextTransport, 'enqueueError'>;
|
|
24
|
+
export declare function createRequestErrorHandler(transport: ErrorSink): (error: unknown, request: NextRequestInfo, context: NextRequestErrorContext) => void;
|
|
25
|
+
export declare function requestErrorToInput(error: unknown, request: NextRequestInfo, context: NextRequestErrorContext): StackNextErrorInput;
|
|
26
|
+
//# sourceMappingURL=edge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge.d.ts","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,KAAK,EACV,uBAAuB,EACvB,eAAe,EAChB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACtB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,KAAK,4BAA4B,GAClC,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,uBAAuB,EACvB,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,eAAe,CAAA;AAEtB,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC;AAED,eAAO,MAAM,QAAQ,0BAAoB,CAAA;AAEzC,wBAAgB,wBAAwB,IAAI,qBAAqB,GAAG,SAAS,CAE5E;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEvD;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,GAC/B,IAAI,CAIN;AAED,KAAK,SAAS,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAA;AAEzD,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,GACnB,CACD,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,KAC7B,IAAI,CAGR;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,GAC/B,mBAAmB,CAKrB"}
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { StackNextSpanExporter, readableSpanToInput, spanAttributes, } from './exporter.js';
|
|
2
|
+
export { withStackNextAction, withStackNextSpan, withStackNextSpanSync, } from './manual.js';
|
|
3
|
+
export async function registerStackNext(options = {}) {
|
|
4
|
+
void options;
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
export const register = registerStackNext;
|
|
8
|
+
export function getStackNextRegistration() {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
export async function shutdownStackNext() {
|
|
12
|
+
// Nothing was registered on the Edge runtime.
|
|
13
|
+
}
|
|
14
|
+
export function onRequestError(error, request, context) {
|
|
15
|
+
void error;
|
|
16
|
+
void request;
|
|
17
|
+
void context;
|
|
18
|
+
}
|
|
19
|
+
export function createRequestErrorHandler(transport) {
|
|
20
|
+
void transport;
|
|
21
|
+
return () => undefined;
|
|
22
|
+
}
|
|
23
|
+
export function requestErrorToInput(error, request, context) {
|
|
24
|
+
void error;
|
|
25
|
+
void request;
|
|
26
|
+
void context;
|
|
27
|
+
throw new Error('StackNext request errors are only captured on the Node.js runtime');
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=edge.js.map
|
package/dist/edge.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge.js","sourceRoot":"","sources":["../src/edge.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,GAEf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GAEtB,MAAM,aAAa,CAAA;AAUpB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAoC,EAAE;IAEtC,KAAK,OAAO,CAAA;IACZ,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,iBAAiB,CAAA;AAEzC,MAAM,UAAU,wBAAwB;IACtC,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,8CAA8C;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,KAAc,EACd,OAAwB,EACxB,OAAgC;IAEhC,KAAK,KAAK,CAAA;IACV,KAAK,OAAO,CAAA;IACZ,KAAK,OAAO,CAAA;AACd,CAAC;AAID,MAAM,UAAU,yBAAyB,CACvC,SAAoB;IAMpB,KAAK,SAAS,CAAA;IACd,OAAO,GAAG,EAAE,CAAC,SAAS,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,OAAwB,EACxB,OAAgC;IAEhC,KAAK,KAAK,CAAA;IACV,KAAK,OAAO,CAAA;IACZ,KAAK,OAAO,CAAA;IACZ,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAA;AACtF,CAAC"}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type StackNextErrorInput } from '@stacknext/core';
|
|
2
|
+
import { type StackNextTransport } from '@stacknext/core/transport';
|
|
3
|
+
/** Structural subset of Next.js `Instrumentation.RequestInfo`. */
|
|
4
|
+
export interface NextRequestInfo {
|
|
5
|
+
path: string;
|
|
6
|
+
method: string;
|
|
7
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
8
|
+
}
|
|
9
|
+
/** Structural subset of Next.js `Instrumentation.RequestErrorContext`. */
|
|
10
|
+
export interface NextRequestErrorContext {
|
|
11
|
+
routerKind?: string;
|
|
12
|
+
routePath?: string;
|
|
13
|
+
routeType?: string;
|
|
14
|
+
renderSource?: string;
|
|
15
|
+
revalidateReason?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Converts a Next.js request error into the StackNext wire format.
|
|
20
|
+
* Header values are never read or stored.
|
|
21
|
+
*/
|
|
22
|
+
export declare function requestErrorToInput(error: unknown, request: NextRequestInfo, context: NextRequestErrorContext): StackNextErrorInput;
|
|
23
|
+
type ErrorSink = Pick<StackNextTransport, 'enqueueError'>;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Next.js `onRequestError` handler bound to a transport. Exposed for
|
|
26
|
+
* tests and for applications that want to supply their own transport.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createRequestErrorHandler(transport: ErrorSink): (error: unknown, request: NextRequestInfo, context: NextRequestErrorContext) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Next.js `onRequestError` entry point. Re-export from `instrumentation.ts`.
|
|
31
|
+
* When the active OTel span is available the error is linked to its trace;
|
|
32
|
+
* otherwise the collector correlates by route and timestamp.
|
|
33
|
+
*/
|
|
34
|
+
export declare function onRequestError(error: unknown, request: NextRequestInfo, context: NextRequestErrorContext): void;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAA;AAElC,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;CACxD;AAED,0EAA0E;AAC1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AA+ED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,GAC/B,mBAAmB,CAkCrB;AAED,KAAK,SAAS,GAAG,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAA;AAEzD;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,GACnB,CACD,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,KAC7B,IAAI,CAQR;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,uBAAuB,GAC/B,IAAI,CAWN"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { isSpanContextValid, trace } from '@opentelemetry/api';
|
|
3
|
+
import { redactErrorText, redactText, stripUrlQuery, } from '@stacknext/core';
|
|
4
|
+
import { getStackNextTransport, } from '@stacknext/core/transport';
|
|
5
|
+
function readErrorDetails(error) {
|
|
6
|
+
if (error instanceof Error) {
|
|
7
|
+
const digest = error.digest;
|
|
8
|
+
return {
|
|
9
|
+
message: error.message || 'Unknown error',
|
|
10
|
+
name: error.name || 'Error',
|
|
11
|
+
...(error.stack ? { stack: error.stack } : {}),
|
|
12
|
+
...(typeof digest === 'string' && digest.length > 0
|
|
13
|
+
? { digest: redactText(digest, 1_000) }
|
|
14
|
+
: {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (typeof error === 'string' && error.trim() !== '') {
|
|
18
|
+
return { message: error, name: 'Error' };
|
|
19
|
+
}
|
|
20
|
+
if (typeof error === 'object' && error !== null) {
|
|
21
|
+
const record = error;
|
|
22
|
+
const message = typeof record.message === 'string' && record.message.trim() !== ''
|
|
23
|
+
? record.message
|
|
24
|
+
: 'Unknown error';
|
|
25
|
+
const name = typeof record.name === 'string' && record.name.trim() !== ''
|
|
26
|
+
? record.name
|
|
27
|
+
: 'Error';
|
|
28
|
+
return {
|
|
29
|
+
message,
|
|
30
|
+
name,
|
|
31
|
+
...(typeof record.stack === 'string' ? { stack: record.stack } : {}),
|
|
32
|
+
...(typeof record.digest === 'string' && record.digest.length > 0
|
|
33
|
+
? { digest: redactText(record.digest, 1_000) }
|
|
34
|
+
: {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
message: error === undefined || error === null ? 'Unknown error' : String(error),
|
|
39
|
+
name: 'Error',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function safeText(value, maxLength = 2_000) {
|
|
43
|
+
if (typeof value !== 'string')
|
|
44
|
+
return undefined;
|
|
45
|
+
const trimmed = value.trim();
|
|
46
|
+
if (trimmed === '')
|
|
47
|
+
return undefined;
|
|
48
|
+
return redactText(trimmed, maxLength);
|
|
49
|
+
}
|
|
50
|
+
function safePath(value) {
|
|
51
|
+
const text = safeText(value);
|
|
52
|
+
return text ? stripUrlQuery(text) : undefined;
|
|
53
|
+
}
|
|
54
|
+
function buildAttributes(context) {
|
|
55
|
+
const attributes = {};
|
|
56
|
+
if (typeof context.routerKind === 'string') {
|
|
57
|
+
attributes['next.router_kind'] = context.routerKind;
|
|
58
|
+
}
|
|
59
|
+
if (typeof context.routeType === 'string') {
|
|
60
|
+
attributes['next.route_type'] = context.routeType;
|
|
61
|
+
}
|
|
62
|
+
if (typeof context.renderSource === 'string') {
|
|
63
|
+
attributes['next.render_source'] = context.renderSource;
|
|
64
|
+
}
|
|
65
|
+
if (typeof context.revalidateReason === 'string') {
|
|
66
|
+
attributes['next.revalidate_reason'] = context.revalidateReason;
|
|
67
|
+
}
|
|
68
|
+
return attributes;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Converts a Next.js request error into the StackNext wire format.
|
|
72
|
+
* Header values are never read or stored.
|
|
73
|
+
*/
|
|
74
|
+
export function requestErrorToInput(error, request, context) {
|
|
75
|
+
const details = readErrorDetails(error);
|
|
76
|
+
const route = safePath(context.routePath);
|
|
77
|
+
const requestPath = safePath(request.path);
|
|
78
|
+
const method = safeText(request.method, 32)?.toUpperCase();
|
|
79
|
+
const activeSpan = trace.getActiveSpan();
|
|
80
|
+
const spanContext = activeSpan?.spanContext();
|
|
81
|
+
const validContext = spanContext && isSpanContextValid(spanContext);
|
|
82
|
+
return {
|
|
83
|
+
id: randomUUID(),
|
|
84
|
+
message: redactErrorText(details.message),
|
|
85
|
+
name: redactText(details.name, 200),
|
|
86
|
+
...(details.stack ? { stack: redactErrorText(details.stack) } : {}),
|
|
87
|
+
...(details.digest ? { digest: details.digest } : {}),
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
...(route ? { route } : {}),
|
|
90
|
+
...(method ? { method } : {}),
|
|
91
|
+
...(requestPath ? { requestPath } : {}),
|
|
92
|
+
...(context.routerKind
|
|
93
|
+
? { routerKind: redactText(context.routerKind, 100) }
|
|
94
|
+
: {}),
|
|
95
|
+
...(context.routeType ? { routeType: redactText(context.routeType, 100) } : {}),
|
|
96
|
+
...(context.renderSource
|
|
97
|
+
? { renderSource: redactText(context.renderSource, 100) }
|
|
98
|
+
: {}),
|
|
99
|
+
...(validContext
|
|
100
|
+
? {
|
|
101
|
+
traceId: spanContext.traceId.toLowerCase(),
|
|
102
|
+
spanId: spanContext.spanId.toLowerCase(),
|
|
103
|
+
}
|
|
104
|
+
: {}),
|
|
105
|
+
attributes: buildAttributes(context),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates a Next.js `onRequestError` handler bound to a transport. Exposed for
|
|
110
|
+
* tests and for applications that want to supply their own transport.
|
|
111
|
+
*/
|
|
112
|
+
export function createRequestErrorHandler(transport) {
|
|
113
|
+
return (error, request, context) => {
|
|
114
|
+
try {
|
|
115
|
+
transport.enqueueError(requestErrorToInput(error, request, context));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Instrumentation must never throw into Next.js error handling.
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Next.js `onRequestError` entry point. Re-export from `instrumentation.ts`.
|
|
124
|
+
* When the active OTel span is available the error is linked to its trace;
|
|
125
|
+
* otherwise the collector correlates by route and timestamp.
|
|
126
|
+
*/
|
|
127
|
+
export function onRequestError(error, request, context) {
|
|
128
|
+
const runtime = process.env.NEXT_RUNTIME;
|
|
129
|
+
if (runtime !== undefined && runtime !== 'nodejs')
|
|
130
|
+
return;
|
|
131
|
+
if (process.env.STACKNEXT_CAPTURE_ERRORS === '0')
|
|
132
|
+
return;
|
|
133
|
+
try {
|
|
134
|
+
const transport = getStackNextTransport();
|
|
135
|
+
if (!transport)
|
|
136
|
+
return;
|
|
137
|
+
createRequestErrorHandler(transport)(error, request, context);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Never break the host application because telemetry failed.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Attributes, type Span } from '@opentelemetry/api';
|
|
2
|
+
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { type StackNextSpanInput } from '@stacknext/core';
|
|
4
|
+
import type { StackNextTransport } from '@stacknext/core/transport';
|
|
5
|
+
/**
|
|
6
|
+
* Converts an OTel ReadableSpan into the StackNext wire format. Redaction happens
|
|
7
|
+
* here as well as in the collector: sensitive data never leaves the app process.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readableSpanToInput(span: ReadableSpan, options: {
|
|
10
|
+
collectorUrl: string;
|
|
11
|
+
fallbackServiceName: string;
|
|
12
|
+
}): StackNextSpanInput | null;
|
|
13
|
+
export interface StackNextSpanExporterOptions {
|
|
14
|
+
transport: Pick<StackNextTransport, 'enqueueSpan' | 'forceFlush'>;
|
|
15
|
+
collectorUrl: string;
|
|
16
|
+
fallbackServiceName?: string;
|
|
17
|
+
/** Set to false to drop Next.js fetch spans. */
|
|
18
|
+
captureFetch?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* OpenTelemetry SpanExporter that hands normalized spans to the bounded local
|
|
22
|
+
* transport. It never performs network I/O synchronously.
|
|
23
|
+
*/
|
|
24
|
+
export declare class StackNextSpanExporter implements SpanExporter {
|
|
25
|
+
private readonly transport;
|
|
26
|
+
private readonly collectorUrl;
|
|
27
|
+
private readonly fallbackServiceName;
|
|
28
|
+
private readonly captureFetch;
|
|
29
|
+
constructor(options: StackNextSpanExporterOptions);
|
|
30
|
+
export(spans: ReadableSpan[], resultCallback: (result: {
|
|
31
|
+
code: number;
|
|
32
|
+
error?: Error;
|
|
33
|
+
}) => void): void;
|
|
34
|
+
shutdown(): Promise<void>;
|
|
35
|
+
forceFlush(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare function spanAttributes(attributes: Record<string, unknown> | undefined): Attributes;
|
|
38
|
+
export type { Span };
|
|
39
|
+
//# sourceMappingURL=exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC/E,OAAO,EAML,KAAK,kBAAkB,EAGxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AA8DnE;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE;IACP,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;CAC5B,GACA,kBAAkB,GAAG,IAAI,CAsD3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,GAAG,YAAY,CAAC,CAAA;IACjE,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gDAAgD;IAChD,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,YAAY;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwD;IAClF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,OAAO,EAAE,4BAA4B;IAOjD,MAAM,CACJ,KAAK,EAAE,YAAY,EAAE,EACrB,cAAc,EAAE,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,KAAK,IAAI,GAChE,IAAI;IAoBD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAGlC;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC9C,UAAU,CAoBZ;AAED,YAAY,EAAE,IAAI,EAAE,CAAA"}
|
package/dist/exporter.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { SpanKind, SpanStatusCode, } from '@opentelemetry/api';
|
|
2
|
+
import { frameworkSpanFields, redactText, sanitizeAttributes, sanitizeResource, stripUrlQueryInText, } from '@stacknext/core';
|
|
3
|
+
const TRACE_ID_PATTERN = /^[0-9a-f]{32}$/i;
|
|
4
|
+
const SPAN_ID_PATTERN = /^[0-9a-f]{16}$/i;
|
|
5
|
+
function hrTimeToMilliseconds(time) {
|
|
6
|
+
const [seconds, nanoseconds] = time;
|
|
7
|
+
return seconds * 1_000 + nanoseconds / 1_000_000;
|
|
8
|
+
}
|
|
9
|
+
function kindFromOtel(kind) {
|
|
10
|
+
switch (kind) {
|
|
11
|
+
case SpanKind.SERVER:
|
|
12
|
+
return 'server';
|
|
13
|
+
case SpanKind.CLIENT:
|
|
14
|
+
return 'client';
|
|
15
|
+
case SpanKind.PRODUCER:
|
|
16
|
+
return 'producer';
|
|
17
|
+
case SpanKind.CONSUMER:
|
|
18
|
+
return 'consumer';
|
|
19
|
+
case SpanKind.INTERNAL:
|
|
20
|
+
return 'internal';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function statusFromOtel(status) {
|
|
24
|
+
if (status.code === SpanStatusCode.OK)
|
|
25
|
+
return 'ok';
|
|
26
|
+
if (status.code === SpanStatusCode.ERROR)
|
|
27
|
+
return 'error';
|
|
28
|
+
return 'unset';
|
|
29
|
+
}
|
|
30
|
+
function attributeString(attributes, key) {
|
|
31
|
+
const value = attributes[key];
|
|
32
|
+
return typeof value === 'string' ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
function isInternalUrl(attributes, collectorUrl) {
|
|
35
|
+
const candidates = [
|
|
36
|
+
attributeString(attributes, 'http.url'),
|
|
37
|
+
attributeString(attributes, 'url.full'),
|
|
38
|
+
attributeString(attributes, 'http.target'),
|
|
39
|
+
attributeString(attributes, 'next.original_url'),
|
|
40
|
+
].filter((value) => Boolean(value));
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
if (candidate.includes('/__stacknext/'))
|
|
43
|
+
return true;
|
|
44
|
+
try {
|
|
45
|
+
if (collectorUrl && candidate.startsWith(new URL(collectorUrl).origin)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore malformed URLs in attributes.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Converts an OTel ReadableSpan into the StackNext wire format. Redaction happens
|
|
57
|
+
* here as well as in the collector: sensitive data never leaves the app process.
|
|
58
|
+
*/
|
|
59
|
+
export function readableSpanToInput(span, options) {
|
|
60
|
+
const context = span.spanContext();
|
|
61
|
+
if (!TRACE_ID_PATTERN.test(context.traceId) ||
|
|
62
|
+
!SPAN_ID_PATTERN.test(context.spanId)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const attributes = sanitizeAttributes(span.attributes);
|
|
66
|
+
const nextSpanType = attributeString(attributes, 'next.span_type');
|
|
67
|
+
if (nextSpanType?.startsWith('Instrumentation.'))
|
|
68
|
+
return null;
|
|
69
|
+
if (isInternalUrl(attributes, options.collectorUrl))
|
|
70
|
+
return null;
|
|
71
|
+
const framework = frameworkSpanFields(attributes);
|
|
72
|
+
const resource = sanitizeResource(span.resource.attributes);
|
|
73
|
+
const serviceName = typeof resource['service.name'] === 'string'
|
|
74
|
+
? resource['service.name']
|
|
75
|
+
: options.fallbackServiceName;
|
|
76
|
+
const startTime = hrTimeToMilliseconds(span.startTime);
|
|
77
|
+
const endTime = Math.max(startTime, hrTimeToMilliseconds(span.endTime));
|
|
78
|
+
const status = statusFromOtel(span.status);
|
|
79
|
+
const parentSpanId = span.parentSpanContext?.spanId;
|
|
80
|
+
const safeName = stripUrlQueryInText(redactText(span.name, 512));
|
|
81
|
+
return {
|
|
82
|
+
traceId: context.traceId.toLowerCase(),
|
|
83
|
+
spanId: context.spanId.toLowerCase(),
|
|
84
|
+
...(parentSpanId && SPAN_ID_PATTERN.test(parentSpanId)
|
|
85
|
+
? { parentSpanId: parentSpanId.toLowerCase() }
|
|
86
|
+
: {}),
|
|
87
|
+
name: safeName,
|
|
88
|
+
kind: kindFromOtel(span.kind),
|
|
89
|
+
startTime,
|
|
90
|
+
endTime,
|
|
91
|
+
durationMs: endTime - startTime,
|
|
92
|
+
status,
|
|
93
|
+
...(span.status.message
|
|
94
|
+
? {
|
|
95
|
+
statusMessage: stripUrlQueryInText(redactText(span.status.message, 8_000)),
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
serviceName: redactText(serviceName, 200),
|
|
99
|
+
...(nextSpanType ? { nextSpanType: redactText(nextSpanType, 200) } : {}),
|
|
100
|
+
...framework,
|
|
101
|
+
attributes,
|
|
102
|
+
events: span.events.slice(0, 100).map((event) => ({
|
|
103
|
+
name: stripUrlQueryInText(redactText(event.name, 512)),
|
|
104
|
+
time: hrTimeToMilliseconds(event.time),
|
|
105
|
+
attributes: sanitizeAttributes(event.attributes ?? {}),
|
|
106
|
+
})),
|
|
107
|
+
resource,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* OpenTelemetry SpanExporter that hands normalized spans to the bounded local
|
|
112
|
+
* transport. It never performs network I/O synchronously.
|
|
113
|
+
*/
|
|
114
|
+
export class StackNextSpanExporter {
|
|
115
|
+
transport;
|
|
116
|
+
collectorUrl;
|
|
117
|
+
fallbackServiceName;
|
|
118
|
+
captureFetch;
|
|
119
|
+
constructor(options) {
|
|
120
|
+
this.transport = options.transport;
|
|
121
|
+
this.collectorUrl = options.collectorUrl;
|
|
122
|
+
this.fallbackServiceName = options.fallbackServiceName ?? 'next-app';
|
|
123
|
+
this.captureFetch = options.captureFetch ?? true;
|
|
124
|
+
}
|
|
125
|
+
export(spans, resultCallback) {
|
|
126
|
+
try {
|
|
127
|
+
for (const span of spans) {
|
|
128
|
+
const input = readableSpanToInput(span, {
|
|
129
|
+
collectorUrl: this.collectorUrl,
|
|
130
|
+
fallbackServiceName: this.fallbackServiceName,
|
|
131
|
+
});
|
|
132
|
+
if (!input)
|
|
133
|
+
continue;
|
|
134
|
+
if (!this.captureFetch && input.nextSpanType === 'AppRender.fetch')
|
|
135
|
+
continue;
|
|
136
|
+
this.transport.enqueueSpan(input);
|
|
137
|
+
}
|
|
138
|
+
resultCallback({ code: 0 });
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
resultCallback({
|
|
142
|
+
code: 1,
|
|
143
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async shutdown() {
|
|
148
|
+
await this.transport.forceFlush();
|
|
149
|
+
}
|
|
150
|
+
async forceFlush() {
|
|
151
|
+
await this.transport.forceFlush();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function spanAttributes(attributes) {
|
|
155
|
+
const sanitized = sanitizeAttributes(attributes);
|
|
156
|
+
const output = {};
|
|
157
|
+
for (const [key, value] of Object.entries(sanitized)) {
|
|
158
|
+
if (typeof value === 'string' ||
|
|
159
|
+
typeof value === 'number' ||
|
|
160
|
+
typeof value === 'boolean' ||
|
|
161
|
+
(Array.isArray(value) &&
|
|
162
|
+
value.every((item) => typeof item === 'string' ||
|
|
163
|
+
typeof item === 'number' ||
|
|
164
|
+
typeof item === 'boolean'))) {
|
|
165
|
+
output[key] = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return output;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=exporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { StackNextSpanExporter, readableSpanToInput, spanAttributes, type StackNextSpanExporterOptions, } from './exporter.js';
|
|
2
|
+
export { getStackNextRegistration, register, registerStackNext, shutdownStackNext, type StackNextRegisterOptions, type StackNextRegistration, } from './register.js';
|
|
3
|
+
export { createRequestErrorHandler, onRequestError, requestErrorToInput, type NextRequestErrorContext, type NextRequestInfo, } from './error.js';
|
|
4
|
+
export { withStackNextSpan, withStackNextSpanSync, withStackNextAction, type StackNextManualSpanOptions, } from './manual.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,KAAK,4BAA4B,GAClC,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { StackNextSpanExporter, readableSpanToInput, spanAttributes, } from './exporter.js';
|
|
2
|
+
export { getStackNextRegistration, register, registerStackNext, shutdownStackNext, } from './register.js';
|
|
3
|
+
export { createRequestErrorHandler, onRequestError, requestErrorToInput, } from './error.js';
|
|
4
|
+
export { withStackNextSpan, withStackNextSpanSync, withStackNextAction, } from './manual.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,GAEf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,wBAAwB,EACxB,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,GAGlB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,yBAAyB,EACzB,cAAc,EACd,mBAAmB,GAGpB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,GAEpB,MAAM,aAAa,CAAA"}
|
package/dist/manual.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SpanKind, type Span } from '@opentelemetry/api';
|
|
2
|
+
export interface StackNextManualSpanOptions {
|
|
3
|
+
attributes?: Record<string, unknown>;
|
|
4
|
+
kind?: SpanKind;
|
|
5
|
+
/** Record thrown exceptions on the span. Defaults to true. */
|
|
6
|
+
recordException?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type ManualSpanFunction<T> = (span: Span) => T | Promise<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Runs an async operation inside a StackNext span. The span is exported along
|
|
11
|
+
* with Next.js request spans, so manual work appears in the same timeline.
|
|
12
|
+
*
|
|
13
|
+
* Both `(name, fn, options?)` and `(name, options, fn)` are supported.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withStackNextSpan<T>(name: string, fn: ManualSpanFunction<T>, options?: StackNextManualSpanOptions): Promise<T>;
|
|
16
|
+
export declare function withStackNextSpan<T>(name: string, options: StackNextManualSpanOptions, fn: ManualSpanFunction<T>): Promise<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Synchronous variant for database drivers and other blocking work. Supports
|
|
19
|
+
* the same argument forms as {@link withStackNextSpan}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function withStackNextSpanSync<T>(name: string, fn: (span: Span) => T, options?: StackNextManualSpanOptions): T;
|
|
22
|
+
export declare function withStackNextSpanSync<T>(name: string, options: StackNextManualSpanOptions, fn: (span: Span) => T): T;
|
|
23
|
+
/**
|
|
24
|
+
* Wraps a Server Action in an explicit span. Successful Server Actions do not
|
|
25
|
+
* reach `onRequestError`, so this helper is the supported way to make them
|
|
26
|
+
* visible. Return values and thrown control flow are preserved.
|
|
27
|
+
*/
|
|
28
|
+
export declare function withStackNextAction<T>(name: string, fn: ManualSpanFunction<T>, options?: StackNextManualSpanOptions): Promise<T>;
|
|
29
|
+
export declare function withStackNextAction<T>(name: string, options: StackNextManualSpanOptions, fn: ManualSpanFunction<T>): Promise<T>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=manual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.d.ts","sourceRoot":"","sources":["../src/manual.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAIR,KAAK,IAAI,EACV,MAAM,oBAAoB,CAAA;AAQ3B,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,8DAA8D;IAC9D,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,KAAK,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAqF3D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,0BAA0B,GACnC,OAAO,CAAC,CAAC,CAAC,CAAA;AACb,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,EACnC,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,CAAC,CAAA;AAmBb;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,EACrB,OAAO,CAAC,EAAE,0BAA0B,GACnC,CAAC,CAAA;AACJ,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,EACnC,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,GACpB,CAAC,CAAA;AAmBJ;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,0BAA0B,GACnC,OAAO,CAAC,CAAC,CAAC,CAAA;AACb,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,EACnC,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,CAAC,CAAA"}
|
package/dist/manual.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SpanKind, SpanStatusCode, trace, } from '@opentelemetry/api';
|
|
2
|
+
import { STACKNEXT_SDK_VERSION, redactText, stripUrlQueryInText, } from '@stacknext/core';
|
|
3
|
+
import { spanAttributes } from './exporter.js';
|
|
4
|
+
function tracer() {
|
|
5
|
+
return trace.getTracer('stacknext', STACKNEXT_SDK_VERSION);
|
|
6
|
+
}
|
|
7
|
+
function startOptions(options, marker) {
|
|
8
|
+
return {
|
|
9
|
+
kind: options.kind ?? SpanKind.INTERNAL,
|
|
10
|
+
attributes: {
|
|
11
|
+
...spanAttributes(options.attributes),
|
|
12
|
+
// Set after user attributes so coverage and classification cannot be
|
|
13
|
+
// spoofed or accidentally dropped by a caller-provided map.
|
|
14
|
+
'stacknext.manual': true,
|
|
15
|
+
'stacknext.span.kind': marker,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function safeErrorText(value, maxLength) {
|
|
20
|
+
return stripUrlQueryInText(redactText(value, maxLength));
|
|
21
|
+
}
|
|
22
|
+
function safeError(error) {
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return {
|
|
25
|
+
name: safeErrorText(error.name, 200),
|
|
26
|
+
message: safeErrorText(error.message, 8_000),
|
|
27
|
+
...(error.stack ? { stack: safeErrorText(error.stack, 64_000) } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { name: 'Error', message: safeErrorText(String(error), 8_000) };
|
|
31
|
+
}
|
|
32
|
+
function recordFailure(span, error) {
|
|
33
|
+
const safe = safeError(error);
|
|
34
|
+
span.recordException(safe);
|
|
35
|
+
span.setStatus({
|
|
36
|
+
code: SpanStatusCode.ERROR,
|
|
37
|
+
message: safe.message.slice(0, 512),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Redirects and not-found responses use thrown control-flow objects in the App
|
|
42
|
+
* Router. They are normal outcomes, not failures, but must still propagate.
|
|
43
|
+
*/
|
|
44
|
+
function isControlFlowError(error) {
|
|
45
|
+
if (typeof error !== 'object' || error === null)
|
|
46
|
+
return false;
|
|
47
|
+
const digest = error.digest;
|
|
48
|
+
return (typeof digest === 'string' &&
|
|
49
|
+
(digest === 'NEXT_NOT_FOUND' ||
|
|
50
|
+
digest.startsWith('NEXT_REDIRECT') ||
|
|
51
|
+
digest.startsWith('NEXT_HTTP_ERROR_FALLBACK')));
|
|
52
|
+
}
|
|
53
|
+
function resolveCall(second, third) {
|
|
54
|
+
if (typeof second === 'function') {
|
|
55
|
+
return {
|
|
56
|
+
fn: second,
|
|
57
|
+
options: third ?? {},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (typeof third !== 'function') {
|
|
61
|
+
throw new TypeError('withStackNextSpan requires a callback function');
|
|
62
|
+
}
|
|
63
|
+
return { fn: third, options: second };
|
|
64
|
+
}
|
|
65
|
+
function finishFailure(span, error, options) {
|
|
66
|
+
if (options.recordException !== false && !isControlFlowError(error)) {
|
|
67
|
+
recordFailure(span, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function withStackNextSpan(name, second, third) {
|
|
71
|
+
const { fn, options } = resolveCall(second, third);
|
|
72
|
+
return tracer().startActiveSpan(name, startOptions(options, 'custom'), async (span) => {
|
|
73
|
+
try {
|
|
74
|
+
return await fn(span);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
finishFailure(span, error, options);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
span.end();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export function withStackNextSpanSync(name, second, third) {
|
|
86
|
+
const { fn, options } = resolveCall(second, third);
|
|
87
|
+
return tracer().startActiveSpan(name, startOptions(options, 'custom'), (span) => {
|
|
88
|
+
try {
|
|
89
|
+
return fn(span);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
finishFailure(span, error, options);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
span.end();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function withStackNextAction(name, second, third) {
|
|
101
|
+
const { fn, options } = resolveCall(second, third);
|
|
102
|
+
return tracer().startActiveSpan(name, startOptions(options, 'action'), async (span) => {
|
|
103
|
+
try {
|
|
104
|
+
return await fn(span);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
finishFailure(span, error, options);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
span.end();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=manual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../src/manual.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
2
|
+
import { type StackNextTransport, type StackNextTransportOptions } from '@stacknext/core/transport';
|
|
3
|
+
import { StackNextSpanExporter } from './exporter.js';
|
|
4
|
+
export interface StackNextRegisterOptions {
|
|
5
|
+
/** Overrides environment-based transport configuration. */
|
|
6
|
+
transport?: StackNextTransportOptions;
|
|
7
|
+
/** Collector URL; used to filter self-instrumentation requests. */
|
|
8
|
+
collectorUrl?: string;
|
|
9
|
+
serviceName?: string;
|
|
10
|
+
projectName?: string;
|
|
11
|
+
nextVersion?: string;
|
|
12
|
+
/** Set to false to drop Next.js fetch spans. Defaults to true. */
|
|
13
|
+
captureFetch?: boolean;
|
|
14
|
+
/** Set to false to keep StackNext loaded but inactive. */
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface StackNextRegistration {
|
|
18
|
+
provider: NodeTracerProvider;
|
|
19
|
+
transport: StackNextTransport;
|
|
20
|
+
exporter: StackNextSpanExporter;
|
|
21
|
+
serviceName: string;
|
|
22
|
+
registeredAt: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Starts OpenTelemetry span collection for a Next.js Node.js runtime.
|
|
26
|
+
*
|
|
27
|
+
* Call this from `instrumentation.ts` `register()`. It is inert when StackNext
|
|
28
|
+
* environment variables are absent, so applications can deploy the same build
|
|
29
|
+
* without a local collector. Existing tracer providers are never replaced:
|
|
30
|
+
* StackNext yields to another APM rather than breaking its instrumentation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerStackNext(options?: StackNextRegisterOptions): Promise<StackNextRegistration | null>;
|
|
33
|
+
/** Returns the active registration without creating one. */
|
|
34
|
+
export declare function getStackNextRegistration(): StackNextRegistration | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Flushes buffered spans and shuts the provider down. Safe to call more than
|
|
37
|
+
* once. Next owns process signals; StackNext only hooks process exit.
|
|
38
|
+
*/
|
|
39
|
+
export declare function shutdownStackNext(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Next.js `register()` entry point. Re-export this from `instrumentation.ts`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function register(options?: StackNextRegisterOptions): Promise<StackNextRegistration | null>;
|
|
44
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAQlE,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC/B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,MAAM,WAAW,wBAAwB;IACvC,2DAA2D;IAC3D,SAAS,CAAC,EAAE,yBAAyB,CAAA;IACrC,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,SAAS,EAAE,kBAAkB,CAAA;IAC7B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;CACrB;AAkFD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA2EvC;AAED,4DAA4D;AAC5D,wBAAgB,wBAAwB,IACpC,qBAAqB,GACrB,SAAS,CAEZ;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBvD;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAEvC"}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ProxyTracerProvider, propagation, trace, } from '@opentelemetry/api';
|
|
2
|
+
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, } from '@opentelemetry/core';
|
|
3
|
+
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
4
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
5
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
6
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from '@opentelemetry/semantic-conventions';
|
|
7
|
+
import { STACKNEXT_SDK_VERSION, } from '@stacknext/core';
|
|
8
|
+
import { getStackNextTransport, } from '@stacknext/core/transport';
|
|
9
|
+
import { StackNextSpanExporter } from './exporter.js';
|
|
10
|
+
function globalState() {
|
|
11
|
+
return globalThis;
|
|
12
|
+
}
|
|
13
|
+
function debugLog(message) {
|
|
14
|
+
if (process.env.STACKNEXT_DEBUG === '1') {
|
|
15
|
+
console.warn(`[stacknext] ${message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the provider currently serving the OTel API, or null when only the
|
|
20
|
+
* API's no-op proxy is installed. `trace.getTracerProvider()` returns the proxy
|
|
21
|
+
* even after registration, so the delegate must be inspected.
|
|
22
|
+
*/
|
|
23
|
+
function registeredProvider() {
|
|
24
|
+
const provider = trace.getTracerProvider();
|
|
25
|
+
if (provider instanceof ProxyTracerProvider) {
|
|
26
|
+
const delegate = provider.getDelegate();
|
|
27
|
+
return delegate.constructor?.name === 'NoopTracerProvider' ? null : delegate;
|
|
28
|
+
}
|
|
29
|
+
if (provider.constructor?.name === 'ProxyTracerProvider') {
|
|
30
|
+
const delegate = provider.getDelegate();
|
|
31
|
+
return delegate.constructor?.name === 'NoopTracerProvider' ? null : delegate;
|
|
32
|
+
}
|
|
33
|
+
return provider;
|
|
34
|
+
}
|
|
35
|
+
function isNodeRuntime() {
|
|
36
|
+
const runtime = process.env.NEXT_RUNTIME;
|
|
37
|
+
return runtime === undefined || runtime === 'nodejs';
|
|
38
|
+
}
|
|
39
|
+
function readNextVersion(options) {
|
|
40
|
+
const value = options.nextVersion ??
|
|
41
|
+
process.env.STACKNEXT_NEXT_VERSION ??
|
|
42
|
+
process.env.npm_package_dependencies_next ??
|
|
43
|
+
undefined;
|
|
44
|
+
return value && value.trim() !== '' ? value.trim() : undefined;
|
|
45
|
+
}
|
|
46
|
+
function buildResource(serviceName, nextVersion) {
|
|
47
|
+
const serviceVersion = process.env.npm_package_version && process.env.npm_package_version.trim() !== ''
|
|
48
|
+
? process.env.npm_package_version.trim()
|
|
49
|
+
: STACKNEXT_SDK_VERSION;
|
|
50
|
+
return resourceFromAttributes({
|
|
51
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
52
|
+
[ATTR_SERVICE_VERSION]: serviceVersion,
|
|
53
|
+
'next.runtime': 'nodejs',
|
|
54
|
+
...(nextVersion ? { 'next.version': nextVersion } : {}),
|
|
55
|
+
...(process.env.NODE_ENV ? { 'deployment.environment': process.env.NODE_ENV } : {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function resolveTransport(options) {
|
|
59
|
+
if (options.transport) {
|
|
60
|
+
return getStackNextTransport({
|
|
61
|
+
...options.transport,
|
|
62
|
+
...(options.serviceName ? { serviceName: options.serviceName } : {}),
|
|
63
|
+
...(options.projectName ? { projectName: options.projectName } : {}),
|
|
64
|
+
...(readNextVersion(options) ? { nextVersion: readNextVersion(options) } : {}),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return getStackNextTransport();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Starts OpenTelemetry span collection for a Next.js Node.js runtime.
|
|
71
|
+
*
|
|
72
|
+
* Call this from `instrumentation.ts` `register()`. It is inert when StackNext
|
|
73
|
+
* environment variables are absent, so applications can deploy the same build
|
|
74
|
+
* without a local collector. Existing tracer providers are never replaced:
|
|
75
|
+
* StackNext yields to another APM rather than breaking its instrumentation.
|
|
76
|
+
*/
|
|
77
|
+
export async function registerStackNext(options = {}) {
|
|
78
|
+
const state = globalState();
|
|
79
|
+
if (state.__stacknextRegistration)
|
|
80
|
+
return state.__stacknextRegistration;
|
|
81
|
+
if (options.enabled === false)
|
|
82
|
+
return null;
|
|
83
|
+
if (!isNodeRuntime())
|
|
84
|
+
return null;
|
|
85
|
+
if (registeredProvider()) {
|
|
86
|
+
debugLog('an OpenTelemetry tracer provider is already registered; leaving it untouched');
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const transport = resolveTransport(options);
|
|
90
|
+
if (!transport) {
|
|
91
|
+
debugLog('STACKNEXT_COLLECTOR_URL and STACKNEXT_TOKEN are not set; tracing is disabled');
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const nextVersion = readNextVersion(options);
|
|
95
|
+
const serviceName = options.serviceName ??
|
|
96
|
+
transport.options.serviceName ??
|
|
97
|
+
transport.options.projectName ??
|
|
98
|
+
'next-app';
|
|
99
|
+
const collectorUrl = options.collectorUrl ?? transport.options.collectorUrl;
|
|
100
|
+
const exporter = new StackNextSpanExporter({
|
|
101
|
+
transport,
|
|
102
|
+
collectorUrl,
|
|
103
|
+
fallbackServiceName: serviceName,
|
|
104
|
+
captureFetch: options.captureFetch ?? process.env.STACKNEXT_CAPTURE_FETCH !== '0',
|
|
105
|
+
});
|
|
106
|
+
const processor = new BatchSpanProcessor(exporter, {
|
|
107
|
+
scheduledDelayMillis: 200,
|
|
108
|
+
maxQueueSize: 2_048,
|
|
109
|
+
maxExportBatchSize: 256,
|
|
110
|
+
exportTimeoutMillis: 2_000,
|
|
111
|
+
});
|
|
112
|
+
const tracerProvider = new NodeTracerProvider({
|
|
113
|
+
resource: buildResource(serviceName, nextVersion),
|
|
114
|
+
spanProcessors: [processor],
|
|
115
|
+
});
|
|
116
|
+
tracerProvider.register();
|
|
117
|
+
// Next.js extracts W3C context from the request headers through the global
|
|
118
|
+
// propagator. Installing a real propagator makes an incoming `traceparent`
|
|
119
|
+
// from a local test runner the parent of the Next.js request trace. Without
|
|
120
|
+
// it the API's no-op propagator silently drops the header.
|
|
121
|
+
propagation.setGlobalPropagator(new CompositePropagator({
|
|
122
|
+
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
|
|
123
|
+
}));
|
|
124
|
+
const registration = {
|
|
125
|
+
provider: tracerProvider,
|
|
126
|
+
transport,
|
|
127
|
+
exporter,
|
|
128
|
+
serviceName,
|
|
129
|
+
registeredAt: Date.now(),
|
|
130
|
+
};
|
|
131
|
+
state.__stacknextRegistration = registration;
|
|
132
|
+
const beforeExit = () => {
|
|
133
|
+
void shutdownStackNext();
|
|
134
|
+
};
|
|
135
|
+
state.__stacknextBeforeExit = beforeExit;
|
|
136
|
+
process.once('beforeExit', beforeExit);
|
|
137
|
+
debugLog(`tracing registered for ${serviceName} (queue ${transport.instanceId.slice(0, 8)})`);
|
|
138
|
+
return registration;
|
|
139
|
+
}
|
|
140
|
+
/** Returns the active registration without creating one. */
|
|
141
|
+
export function getStackNextRegistration() {
|
|
142
|
+
return globalState().__stacknextRegistration;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Flushes buffered spans and shuts the provider down. Safe to call more than
|
|
146
|
+
* once. Next owns process signals; StackNext only hooks process exit.
|
|
147
|
+
*/
|
|
148
|
+
export async function shutdownStackNext() {
|
|
149
|
+
const state = globalState();
|
|
150
|
+
const registration = state.__stacknextRegistration;
|
|
151
|
+
if (!registration)
|
|
152
|
+
return;
|
|
153
|
+
state.__stacknextRegistration = undefined;
|
|
154
|
+
if (state.__stacknextBeforeExit) {
|
|
155
|
+
process.removeListener('beforeExit', state.__stacknextBeforeExit);
|
|
156
|
+
state.__stacknextBeforeExit = undefined;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
await registration.provider.shutdown();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
debugLog(`provider shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
await registration.transport.shutdown(1_000);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Transport shutdown is best effort; the process is exiting.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Next.js `register()` entry point. Re-export this from `instrumentation.ts`.
|
|
173
|
+
*/
|
|
174
|
+
export async function register(options = {}) {
|
|
175
|
+
return registerStackNext(options);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacknext/next",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Next.js instrumentation and OpenTelemetry exporter for StackNext",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"edge": "./dist/edge.js",
|
|
16
|
+
"edge-light": "./dist/edge.js",
|
|
17
|
+
"worker": "./dist/edge.js",
|
|
18
|
+
"workerd": "./dist/edge.js",
|
|
19
|
+
"node": "./dist/index.js",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@stacknext/core": "0.6.1",
|
|
27
|
+
"@opentelemetry/api": "1.9.0",
|
|
28
|
+
"@opentelemetry/context-async-hooks": "2.12.0",
|
|
29
|
+
"@opentelemetry/core": "2.12.0",
|
|
30
|
+
"@opentelemetry/resources": "2.12.0",
|
|
31
|
+
"@opentelemetry/sdk-trace-base": "2.12.0",
|
|
32
|
+
"@opentelemetry/sdk-trace-node": "2.12.0",
|
|
33
|
+
"@opentelemetry/semantic-conventions": "1.39.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.9.0"
|
|
37
|
+
},
|
|
38
|
+
"author": "StackNext contributors",
|
|
39
|
+
"homepage": "https://github.com/marrrkkk/stacknext#readme",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/marrrkkk/stacknext.git",
|
|
43
|
+
"directory": "packages/next"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/marrrkkk/stacknext/issues"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"stacknext",
|
|
50
|
+
"nextjs",
|
|
51
|
+
"opentelemetry",
|
|
52
|
+
"instrumentation",
|
|
53
|
+
"tracing",
|
|
54
|
+
"debugging"
|
|
55
|
+
],
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"next": ">=15.0.0 <17"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc -p tsconfig.build.json",
|
|
64
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
65
|
+
}
|
|
6
66
|
}
|