@zendesk/retrace
Version:
define and capture Product Operation Traces along with computed metrics with an optional friendly React beacon API
404 lines (358 loc) • 11.1 kB
text/typescript
import type { ErrorInfo } from 'react'
import type { BeaconConfig } from './hooksTypes'
import type {
ParentSpanMatcher,
SpanAndAnnotationForMatching,
} from './matchSpan'
import type { TICK_META, TICK_META_END, TickMeta } from './TickParentResolver'
import type { TraceRecording } from './traceRecordingTypes'
import type {
DraftTraceContext,
MapSchemaToTypes,
RelationSchemasBase,
RelationsOnASpan,
Timestamp,
} from './types'
import type { OpenPick, Prettify } from './typeUtils'
export type NativePerformanceEntryType =
| 'element'
| 'event'
| 'first-input'
| 'largest-contentful-paint'
| 'layout-shift'
| 'long-animation-frame'
| 'longtask'
| 'mark'
| 'measure'
| 'navigation'
| 'paint'
| 'resource'
| 'taskattribution'
| 'visibility-state'
export type ComponentLifecycleSpanType =
| 'component-render-start'
| 'component-render'
| 'component-unmount'
| 'hook-render-start'
| 'hook-render'
| 'hook-unmount'
export interface DraftTraceConfig<RelationSchemaT, VariantsT extends string> {
id?: string
parentTraceId?: string
startTime?: Partial<Timestamp>
variant: VariantsT
/**
* any attributes that are relevant to the entire trace
*/
attributes?: Attributes
relatedTo: MapSchemaToTypes<RelationSchemaT> | undefined
/**
* Any additional data that can be used by the tooling to identify the trace
* It will *not* be a part of the trace recording.
*/
// TODO: add typing
baggage?: unknown
}
export interface StartTraceConfig<RelationSchemaT, VariantsT extends string>
extends DraftTraceConfig<RelationSchemaT, VariantsT> {
relatedTo: MapSchemaToTypes<RelationSchemaT>
}
export interface DraftTraceInput<RelationSchemaT, VariantsT extends string>
extends DraftTraceConfig<RelationSchemaT, VariantsT> {
id: string
startTime: Timestamp
}
export interface ActiveTraceInput<RelationSchemaT, VariantsT extends string>
extends DraftTraceInput<RelationSchemaT, VariantsT> {
relatedTo: MapSchemaToTypes<RelationSchemaT>
}
export interface ActiveTraceConfig<
SelectedRelationNameT extends keyof RelationSchemasT,
RelationSchemasT,
VariantsT extends string,
> extends DraftTraceInput<RelationSchemasT[SelectedRelationNameT], VariantsT> {
relatedTo: MapSchemaToTypes<RelationSchemasT[SelectedRelationNameT]>
}
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
export interface Attributes {
[key: string]: unknown
}
export type SpanStatus = 'ok' | 'error'
export type GetParentSpanFn<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> = (
context: GetParentSpanContext<RelationSchemasT>,
/**
* Whether to attempt to resolve parents recursively.
* Enabling this "bakes-in" the PARENT_SPAN reference onto the span
*/
recursive?: boolean,
) => Span<RelationSchemasT> | undefined
export const PARENT_SPAN = Symbol('parentSpan')
export interface SpanBase<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> {
/**
* providing an id is optional, but it will always be present in the recording
* if not provided, it will be generated automatically
*/
id: string
// TODO: allow defining custom spans that extend this SpanBase
type: SpanType | (string & {})
/**
* The common name of the span.
* If converted from a PerformanceEntry, this is a sanitized version of the name.
*/
name: string
startTime: Timestamp
// for non performance entries, relatedTo is set explicitly in the span, something like ticket id or user id
// performance entries can derive relatedTo based using `deriveRelationFromPerformanceEntry`
relatedTo?: RelationsOnASpan<RelationSchemasT>
attributes: Attributes
/**
* The duration of this span.
* If this span is just an event (a point in time), this will be 0.
* On the other hand, spans will have duration > 0.
*/
duration: number
/**
* Status of the span ('error' or 'ok').
*/
status?: SpanStatus
/**
* The original PerformanceEntry from which the Span was created
*/
performanceEntry?: PerformanceEntry
/**
* if status is error, optionally provide the Error object with additional metadata
*/
error?: ErrorLike
/**
* Optional parent span, if known.
* Non-enumerable (symbol).
*/
[PARENT_SPAN]?: Span<RelationSchemasT>
/**
* Resolve parentSpanId after the Trace is completed, or on demand.
* Set internally.
*/
getParentSpan: GetParentSpanFn<RelationSchemasT>
/**
* The ID of the tick in which the span was created.
* This is used to group spans created in the same event loop tick.
*/
tickId?: string
/**
* Metadata about the tick in which the span was created (if tick-tracking functionality enabled).
* Not enumerable (symbol).
* This is used to resolve parent spans across ticks.
*/
[TICK_META]?: TickMeta<RelationSchemasT>
/**
* Metadata about the tick in which the span was ended (if tick-tracking functionality enabled).
* Not enumerable (symbol).
* This is used to resolve parent spans across ticks.
*/
[TICK_META_END]?: TickMeta<RelationSchemasT>
/**
* If true, this span will only be present for matching while the trace is being recorded,
* but will not be included in the final trace recording, unless it is a parent of another span.
* This is useful for internal spans that are not relevant to the final trace.
*/
internalUse?: boolean
}
export interface WithParentSpanMatcher<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> {
/**
* Optional parent span, if known. Takes precedence over getParentSpan.
* Non-enumerable.
*/
parentSpan?: Span<RelationSchemasT>
/**
* A matcher that can be used to find the parent span of this span after the trace is completed.
*/
parentSpanMatcher?: ParentSpanMatcher<
keyof RelationSchemasT,
RelationSchemasT,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any
>
}
export interface ConvenienceSpanProperties<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> extends WithParentSpanMatcher<RelationSchemasT> {
startTime?: Partial<Timestamp>
}
export interface GetParentSpanContext<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> {
thisSpanAndAnnotation: SpanAndAnnotationForMatching<RelationSchemasT>
// TODO: improve types here by requiring SelectedRelationNameT and VariantsT:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
traceContext: DraftTraceContext<any, RelationSchemasT, any> | undefined
}
export interface ComponentRenderSpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
>
// it would be more correct to use 'relatedTo' from BeaconConfig,
// but we'd need to solve some type issues
extends Omit<SpanBase<RelationSchemasT>, 'attributes'>,
Omit<BeaconConfig<RelationSchemasT>, 'relatedTo'> {
type: ComponentLifecycleSpanType
isIdle: boolean
errorInfo?: ErrorInfo
renderCount: number
attributes: NonNullable<BeaconConfig<RelationSchemasT>['attributes']>
}
export type InitiatorType =
| 'audio'
| 'beacon'
| 'body'
| 'css'
| 'early-hint'
| 'embed'
| 'fetch'
| 'frame'
| 'iframe'
| 'icon'
| 'image'
| 'img'
| 'input'
| 'link'
| 'navigation'
| 'object'
| 'ping'
| 'script'
| 'track'
| 'video'
| 'xmlhttprequest'
| 'other'
export interface ResourceSpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> extends SpanBase<RelationSchemasT> {
type: 'resource'
resourceDetails: {
initiatorType: InitiatorType
query: Record<string, string | string[]>
hash: string
}
}
export interface PerformanceEntrySpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> extends SpanBase<RelationSchemasT> {
type: Exclude<NativePerformanceEntryType, 'resource'>
}
/**
* Represents a child operation span within a trace.
* The shape is the same as TraceRecording
*/
export interface ChildOperationSpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> extends Omit<SpanBase<RelationSchemasT>, 'id'>,
Omit<
TraceRecording<keyof RelationSchemasT, RelationSchemasT>,
// these come from the SpanBase type:
'duration' | 'status' | 'relatedTo' | 'entries'
> {
type: 'operation'
}
export interface ErrorLike {
message: string
name?: string
stack?: string
cause?: unknown
}
export interface ErrorSpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> extends SpanBase<RelationSchemasT> {
type: 'error'
error: ErrorLike
status: 'error'
}
/**
* All possible trace entries
*/
export type Span<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> =
| PerformanceEntrySpan<RelationSchemasT>
| ComponentRenderSpan<RelationSchemasT>
| ResourceSpan<RelationSchemasT>
| ChildOperationSpan<RelationSchemasT>
| ErrorSpan<RelationSchemasT>
export type SpanType =
| NativePerformanceEntryType
| ComponentLifecycleSpanType
| 'operation'
| 'error'
export type AutoAddedSpanProperties =
| 'id'
| 'startTime'
| 'attributes'
| 'duration'
| 'getParentSpan'
export type ConvenienceSpan<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
SpanT extends Span<RelationSchemasT>,
> = Prettify<
Omit<SpanT, AutoAddedSpanProperties> &
Partial<Pick<SpanT, 'id' | 'attributes' | 'duration'>> &
ConvenienceSpanProperties<RelationSchemasT>
>
export type ErrorSpanInput<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> = Prettify<
Omit<
ErrorSpan<RelationSchemasT>,
'status' | 'type' | 'name' | AutoAddedSpanProperties
> &
Partial<
Pick<
ErrorSpan<RelationSchemasT>,
'type' | 'name' | 'id' | 'attributes' | 'duration'
> & {
startTime: Partial<Timestamp>
}
> &
ConvenienceSpanProperties<RelationSchemasT>
>
export type PerformanceEntrySpanInput<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> = Prettify<
Omit<PerformanceEntrySpan<RelationSchemasT>, AutoAddedSpanProperties> &
Partial<
Pick<
PerformanceEntrySpan<RelationSchemasT>,
'id' | 'attributes' | 'duration'
> & {
startTime?: Partial<Timestamp>
}
> &
ConvenienceSpanProperties<RelationSchemasT>
>
export type RenderSpanInput<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
> = Prettify<
Omit<ComponentRenderSpan<RelationSchemasT>, AutoAddedSpanProperties> &
Partial<
Pick<
ComponentRenderSpan<RelationSchemasT>,
'id' | 'attributes' | 'duration'
>
> &
ConvenienceSpanProperties<RelationSchemasT>
>
export type UpdatableSpanProperties =
| 'attributes'
| 'relatedTo'
| 'renderedOutput'
| 'isIdle'
export type SpanUpdateFunction<
RelationSchemasT extends RelationSchemasBase<RelationSchemasT>,
SpanT extends Span<RelationSchemasT>,
> = (
spanUpdates: Partial<OpenPick<SpanT, UpdatableSpanProperties>> & {
reprocess?: boolean
},
) => void