Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add _experimental_longtaskNoStartSession flag #899

Merged
merged 5 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .size-limit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ module.exports = [

{
name: 'artifacts/splunk-otel-web.js',
limit: '40 kB',
limit: '41 kB',
path: './packages/web/dist/artifacts/splunk-otel-web.js',
},

{
name: 'artifacts/splunk-otel-web.js',
limit: '72 kB',
limit: '73 kB',
path: './packages/web/dist/artifacts/splunk-otel-web-legacy.js',
},

Expand Down
4 changes: 4 additions & 0 deletions packages/session-recorder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ const SplunkRumRecorder = {
return
}

if (SplunkRum._internalOnExternalSpanCreated) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding it here just to be sure since it is possible that session recorder can have a different version than web agent

SplunkRum._internalOnExternalSpanCreated()
}

// Safeguards from our ingest getting DDOSed:
// 1. A session can send up to 4 hours of data
// 2. Recording resumes on session change if it isn't a background tab (session regenerated in an another tab)
Expand Down
11 changes: 8 additions & 3 deletions packages/web/integration-tests/tests/cookies/cookies.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,27 @@ module.exports = {
}

/*
We are using nip.io to let us test subdomains not sure how reliable it is, so if
We are using nip.io to let us test subdomains not sure how reliable it is, so if
you are debugging flaky test then this should be your first guess.
cookies-domain.ejs has cookieDomain set to 127.0.0.1.nip.io, cookie set via cookieDomain
should be accessible for subdomains also so when we go to test. subdomain we should find the same
should be accessible for subdomains also so when we go to test. subdomain we should find the same
cookie.
*/
const protocol = browser.globals.enableHttps ? 'https' : 'http'
await browser.url(`${protocol}://127.0.0.1.nip.io:${browser.globals.httpPort}/cookies/cookies-domain.ejs`)
const cookie = await browser.getCookie('_splunk_rum_sid')
const cookieParse = decodeURI(cookie.value)

await browser.assert.ok(cookie)

await browser.url(`${protocol}://test.127.0.0.1.nip.io:${browser.globals.httpPort}/cookies/cookies-domain.ejs`)

const cookie2 = await browser.getCookie('_splunk_rum_sid')
const cookie2Parse = decodeURI(cookie2.value)

await browser.assert.strictEqual(cookie.domain, cookie2.domain)
await browser.assert.strictEqual(cookie.value, cookie2.value)
await browser.assert.strictEqual(cookieParse.id, cookie2Parse.id)
await browser.assert.strictEqual(cookieParse.startTime, cookie2Parse.startTime)

await browser.globals.assertNoErrorSpans()
},
Expand Down
8 changes: 1 addition & 7 deletions packages/web/src/SplunkContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@
import { Context, ContextManager, ROOT_CONTEXT } from '@opentelemetry/api'
import { unwrap } from 'shimmer'
import { getOriginalFunction, isFunction, wrapNatively } from './utils'

export interface ContextManagerConfig {
/** Enable async tracking of span parents */
async?: boolean
onBeforeContextEnd?: () => void
onBeforeContextStart?: () => void
}
import { ContextManagerConfig } from './types'

type EventListenerWithOrig = EventListener & { _orig?: EventListener }

Expand Down
18 changes: 17 additions & 1 deletion packages/web/src/SplunkLongTaskInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@
import { InstrumentationBase, InstrumentationConfig } from '@opentelemetry/instrumentation'

import { VERSION } from './version'
import { getCurrentSessionState } from './session'
import { SplunkOtelWebConfig } from './types'

const LONGTASK_PERFORMANCE_TYPE = 'longtask'
const MODULE_NAME = 'splunk-longtask'

export class SplunkLongTaskInstrumentation extends InstrumentationBase {
private _longtaskObserver: PerformanceObserver | undefined

constructor(config: InstrumentationConfig = {}) {
private initOptions: SplunkOtelWebConfig

constructor(config: InstrumentationConfig = {}, initOptions: SplunkOtelWebConfig) {
super(MODULE_NAME, VERSION, Object.assign({}, config))

this.initOptions = initOptions
}

disable(): void {
Expand All @@ -52,6 +58,16 @@ export class SplunkLongTaskInstrumentation extends InstrumentationBase {
init(): void {}

private _createSpanFromEntry(entry: PerformanceEntry) {
if (
!!this.initOptions._experimental_longtaskNoStartSession &&
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the PR... if there is no session we do not spawn new one when this flag is set to true

!getCurrentSessionState({
forceStoreRead: false,
})
) {
// session expired, we do not want to spawn new session from long tasks
return
}

const span = this.tracer.startSpan(LONGTASK_PERFORMANCE_TYPE, {
startTime: entry.startTime,
})
Expand Down
172 changes: 31 additions & 141 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,24 @@
*/

import './polyfill-safari10'
import { InstrumentationConfig, registerInstrumentations } from '@opentelemetry/instrumentation'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import {
ConsoleSpanExporter,
SimpleSpanProcessor,
BatchSpanProcessor,
ReadableSpan,
SpanExporter,
SpanProcessor,
BufferConfig,
AlwaysOffSampler,
AlwaysOnSampler,
ParentBasedSampler,
} from '@opentelemetry/sdk-trace-base'
import { WebTracerConfig } from '@opentelemetry/sdk-trace-web'
import { Attributes, diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'
import { SplunkDocumentLoadInstrumentation } from './SplunkDocumentLoadInstrumentation'
import { SplunkXhrPlugin } from './SplunkXhrPlugin'
import { SplunkFetchInstrumentation } from './SplunkFetchInstrumentation'
import {
SplunkUserInteractionInstrumentation,
SplunkUserInteractionInstrumentationConfig,
DEFAULT_AUTO_INSTRUMENTED_EVENTS,
DEFAULT_AUTO_INSTRUMENTED_EVENT_NAMES,
UserInteractionEventsConfig,
Expand All @@ -46,163 +43,35 @@ import { SplunkExporterConfig } from './exporters/common'
import { SplunkZipkinExporter } from './exporters/zipkin'
import { ERROR_INSTRUMENTATION_NAME, SplunkErrorInstrumentation } from './SplunkErrorInstrumentation'
import { generateId, getPluginConfig } from './utils'
import { getRumSessionId, initSessionTracking } from './session'
import { getRumSessionId, initSessionTracking, updateSessionStatus } from './session'
import { SplunkWebSocketInstrumentation } from './SplunkWebSocketInstrumentation'
import { WebVitalsInstrumentationConfig, initWebVitals } from './webvitals'
import { initWebVitals } from './webvitals'
import { SplunkLongTaskInstrumentation } from './SplunkLongTaskInstrumentation'
import { SplunkPageVisibilityInstrumentation } from './SplunkPageVisibilityInstrumentation'
import { SplunkConnectivityInstrumentation } from './SplunkConnectivityInstrumentation'
import {
SplunkPostDocLoadResourceInstrumentation,
SplunkPostDocLoadResourceInstrumentationConfig,
} from './SplunkPostDocLoadResourceInstrumentation'
import { SplunkPostDocLoadResourceInstrumentation } from './SplunkPostDocLoadResourceInstrumentation'
import { SplunkWebTracerProvider } from './SplunkWebTracerProvider'
import { FetchInstrumentationConfig } from '@opentelemetry/instrumentation-fetch'
import { XMLHttpRequestInstrumentationConfig } from '@opentelemetry/instrumentation-xml-http-request'
import { InternalEventTarget, SplunkOtelWebEventTarget } from './EventTarget'
import { ContextManagerConfig, SplunkContextManager } from './SplunkContextManager'
import { SplunkContextManager } from './SplunkContextManager'
import { Resource, ResourceAttributes } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { SDK_INFO, _globalThis } from '@opentelemetry/core'
import { VERSION } from './version'
import { getSyntheticsRunId, SYNTHETICS_RUN_ID_ATTRIBUTE } from './synthetics'
import { SplunkSpanAttributesProcessor } from './SplunkSpanAttributesProcessor'
import { SessionBasedSampler } from './SessionBasedSampler'
import {
SocketIoClientInstrumentationConfig,
SplunkSocketIoClientInstrumentation,
} from './SplunkSocketIoClientInstrumentation'
import { SplunkSocketIoClientInstrumentation } from './SplunkSocketIoClientInstrumentation'
import { SplunkOTLPTraceExporter } from './exporters/otlp'
import { registerGlobal, unregisterGlobal } from './global-utils'
import { BrowserInstanceService } from './services/BrowserInstanceService'
import { SessionId } from './types'
import { SessionId } from './session'
import { SplunkOtelWebConfig, SplunkOtelWebExporterOptions, SplunkOtelWebOptionsInstrumentations } from './types'

export { SplunkExporterConfig } from './exporters/common'
export { SplunkZipkinExporter } from './exporters/zipkin'
export * from './SplunkWebTracerProvider'
export * from './SessionBasedSampler'

interface SplunkOtelWebOptionsInstrumentations {
connectivity?: boolean | InstrumentationConfig
document?: boolean | InstrumentationConfig
errors?: boolean
fetch?: boolean | FetchInstrumentationConfig
interactions?: boolean | SplunkUserInteractionInstrumentationConfig
longtask?: boolean | InstrumentationConfig
postload?: boolean | SplunkPostDocLoadResourceInstrumentationConfig
socketio?: boolean | SocketIoClientInstrumentationConfig
visibility?: boolean | InstrumentationConfig
websocket?: boolean | InstrumentationConfig
webvitals?: boolean | WebVitalsInstrumentationConfig
xhr?: boolean | XMLHttpRequestInstrumentationConfig
}

export interface SplunkOtelWebExporterOptions {
/**
* Allows remapping Span's attributes right before they're serialized.
* One potential use case of this method is to remove PII from the attributes.
*/
onAttributesSerializing?: (attributes: Attributes, span: ReadableSpan) => Attributes

/**
* Switch from zipkin to otlp for exporting
*/
otlp?: boolean
}

export interface SplunkOtelWebConfig {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to separate file to avoid circular dependency

/**
* If enabled, all spans are treated as activity and extend the duration of the session. Defaults to false.
*/
_experimental_allSpansExtendSession?: boolean

/** Allows http beacon urls */
allowInsecureBeacon?: boolean

/** Application name
* @deprecated Renamed to `applicationName`
*/
app?: string

/** Application name */
applicationName?: string

/** Destination for the captured data */
beaconEndpoint?: string

/**
* Destination for the captured data
* @deprecated Renamed to `beaconEndpoint`, or use realm
*/
beaconUrl?: string

/** Options for context manager */
context?: ContextManagerConfig

/** Sets session cookie to this domain */
cookieDomain?: string

/** Turns on/off internal debug logging */
debug?: boolean

/**
* Sets a value for the `environment` attribute (persists through calls to `setGlobalAttributes()`)
* */
deploymentEnvironment?: string

/**
* Sets a value for the `environment` attribute (persists through calls to `setGlobalAttributes()`)
* @deprecated Renamed to `deploymentEnvironment`
*/
environment?: string

/** Allows configuring how telemetry data is sent to the backend */
exporter?: SplunkOtelWebExporterOptions

/** Sets attributes added to every Span. */
globalAttributes?: Attributes

/**
* Applies for XHR, Fetch and Websocket URLs. URLs that partially match any regex in ignoreUrls will not be traced.
* In addition, URLs that are _exact matches_ of strings in ignoreUrls will also not be traced.
* */
ignoreUrls?: Array<string | RegExp>

/** Configuration for instrumentation modules. */
instrumentations?: SplunkOtelWebOptionsInstrumentations

/**
* The name of your organization’s realm. Automatically configures beaconUrl with correct URL
*/
realm?: string

/**
* Publicly-visible rum access token value. Please do not paste any other access token or auth value into here, as this
* will be visible to every user of your app
*/
rumAccessToken?: string

/**
* Publicly-visible `rumAuth` value. Please do not paste any other access token or auth value into here, as this
* will be visible to every user of your app
* @deprecated Renamed to rumAccessToken
*/
rumAuth?: string

/**
* Config options passed to web tracer
*/
tracer?: WebTracerConfig

/** Use local storage to save session ID instead of cookie */
useLocalStorage?: boolean

/**
* Sets a value for the 'app.version' attribute
*/
version?: string
}

interface SplunkOtelWebConfigInternal extends SplunkOtelWebConfig {
bufferSize?: number
bufferTimeout?: number
Expand Down Expand Up @@ -327,6 +196,11 @@ export interface SplunkOtelWebType extends SplunkOtelWebEventTarget {
*/
_internalInit: (options: Partial<SplunkOtelWebConfigInternal>) => void

/* Used internally by the SplunkSessionRecorder - span from session can extend the session */
_internalOnExternalSpanCreated: () => void

_processedOptions: SplunkOtelWebConfigInternal | null

attributesProcessor?: SplunkSpanAttributesProcessor

deinit: (force?: boolean) => void
Expand Down Expand Up @@ -370,6 +244,8 @@ export const SplunkRum: SplunkOtelWebType = {
ParentBasedSampler,
SessionBasedSampler,

_processedOptions: null,

get inited(): boolean {
return inited
},
Expand Down Expand Up @@ -421,6 +297,8 @@ export const SplunkRum: SplunkOtelWebType = {
},
)

this._processedOptions = processedOptions

if (processedOptions.realm) {
if (!processedOptions.beaconEndpoint) {
processedOptions.beaconEndpoint = getBeaconEndpointForRealm(processedOptions)
Expand Down Expand Up @@ -487,8 +365,12 @@ export const SplunkRum: SplunkOtelWebType = {
const instrumentations = INSTRUMENTATIONS.map(({ Instrument, confKey, disable }) => {
const pluginConf = getPluginConfig(processedOptions.instrumentations[confKey], pluginDefaults, disable)
if (pluginConf) {
// @ts-expect-error Can't mark in any way that processedOptions.instrumentations[confKey] is of specifc config type
const instrumentation = new Instrument(pluginConf)
const instrumentation =
Instrument === SplunkLongTaskInstrumentation
? new Instrument(pluginConf, options)
: // @ts-expect-error Can't mark in any way that processedOptions.instrumentations[confKey] is of specifc config type
new Instrument(pluginConf)

if (confKey === ERROR_INSTRUMENTATION_NAME && instrumentation instanceof SplunkErrorInstrumentation) {
_errorInstrumentation = instrumentation
}
Expand Down Expand Up @@ -633,6 +515,14 @@ export const SplunkRum: SplunkOtelWebType = {
_experimental_getSessionId() {
return this.getSessionId()
},

_internalOnExternalSpanCreated() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gets called from session recorder - when span is created we extend expiration (inactivity) time of session

if (!this._processedOptions) {
return
}

updateSessionStatus({ forceStore: false, useLocalStorage: this._processedOptions.useLocalStorage ?? false })
},
}

export default SplunkRum
Loading
Loading