Skip to content

Commit

Permalink
added auth headers
Browse files Browse the repository at this point in the history
  • Loading branch information
aritro66 committed Apr 22, 2024
1 parent 87947cf commit c8e3c81
Show file tree
Hide file tree
Showing 5 changed files with 424 additions and 227 deletions.
109 changes: 65 additions & 44 deletions packages/session-recorder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { ProxyTracerProvider, TracerProvider, trace, Tracer } from '@opentelemetry/api';
import { record } from 'rrweb';
import OTLPLogExporter from './OTLPLogExporter';
import { BatchLogProcessor, convert } from './BatchLogProcessor';
import { VERSION } from './version';
import { getGlobal } from './utils';

import type { Resource } from '@opentelemetry/resources';
import type { SplunkOtelWebType } from '@splunk/otel-web';
import {
ProxyTracerProvider,
TracerProvider,
trace,
Tracer,
} from "@opentelemetry/api";
import { record } from "rrweb";
import OTLPLogExporter from "./OTLPLogExporter";
import { BatchLogProcessor, convert } from "./BatchLogProcessor";
import { VERSION } from "./version";
import { getGlobal } from "./utils";

import type { Resource } from "@opentelemetry/resources";
import type { SplunkOtelWebType } from "@splunk/otel-web";

interface BasicTracerProvider extends TracerProvider {
readonly resource: Resource;
Expand All @@ -34,7 +39,7 @@ export type SplunkRumRecorderConfig = RRWebOptions & {
/** Destination for the captured data */
beaconEndpoint?: string;

/** Destination for the captured data
/** Destination for the captured data
* @deprecated Use beaconEndpoint
*/
beaconUrl?: string;
Expand All @@ -59,14 +64,18 @@ export type SplunkRumRecorderConfig = RRWebOptions & {

/**
* @deprecated Use RUM token in rumAccessToken
*/
*/
apiToken?: string;

/** Debug mode */
debug?: boolean;
};

function migrateConfigOption(config: SplunkRumRecorderConfig, from: keyof SplunkRumRecorderConfig, to: keyof SplunkRumRecorderConfig) {
function migrateConfigOption(
config: SplunkRumRecorderConfig,
from: keyof SplunkRumRecorderConfig,
to: keyof SplunkRumRecorderConfig
) {
if (from in config && !(to in config)) {
// @ts-expect-error There's no way to type this right
config[to] = config[from];
Expand All @@ -77,13 +86,13 @@ function migrateConfigOption(config: SplunkRumRecorderConfig, from: keyof Splunk
* Update configuration based on configuration option renames
*/
function migrateConfig(config: SplunkRumRecorderConfig) {
migrateConfigOption(config, 'beaconUrl', 'beaconEndpoint');
migrateConfigOption(config, 'rumAuth', 'rumAccessToken');
migrateConfigOption(config, "beaconUrl", "beaconEndpoint");
migrateConfigOption(config, "rumAuth", "rumAccessToken");
return config;
}

// Hard limit of 4 hours of maximum recording during one session
const MAX_RECORDING_LENGTH = ((4 * 60) + 1) * 60 * 1000;
const MAX_RECORDING_LENGTH = (4 * 60 + 1) * 60 * 1000;
const MAX_CHUNK_SIZE = 950 * 1024; // ~950KB
const encoder = new TextEncoder();
const decoder = new TextDecoder();
Expand All @@ -106,29 +115,41 @@ const SplunkRumRecorder = {
return;
}

if (typeof window === 'undefined') {
console.error('Session recorder can\'t be ran in non-browser environments');
if (typeof window === "undefined") {
console.error(
"Session recorder can't be ran in non-browser environments"
);
return;
}

const SplunkRum = getGlobal<SplunkOtelWebType>('splunk.rum');
const SplunkRum = getGlobal<SplunkOtelWebType>("splunk.rum");

let tracerProvider: BasicTracerProvider | ProxyTracerProvider = trace.getTracerProvider() as BasicTracerProvider;
if (tracerProvider && 'getDelegate' in tracerProvider) {
tracerProvider = (tracerProvider as unknown as ProxyTracerProvider).getDelegate() as BasicTracerProvider;
let tracerProvider: BasicTracerProvider | ProxyTracerProvider =
trace.getTracerProvider() as BasicTracerProvider;
if (tracerProvider && "getDelegate" in tracerProvider) {
tracerProvider = (
tracerProvider as unknown as ProxyTracerProvider
).getDelegate() as BasicTracerProvider;
}
if (!SplunkRum || !SplunkRum.resource) {
console.error('Splunk OTEL Web must be inited before session recorder.');
console.error("Splunk OTEL Web must be inited before session recorder.");
return;
}

const resource = SplunkRum.resource;

migrateConfig(config);

const { apiToken, beaconEndpoint, debug, realm, rumAccessToken, ...rrwebConf } = config;
tracer = trace.getTracer('splunk.rr-web', VERSION);
const span = tracer.startSpan('record init');
const {
apiToken,
beaconEndpoint,
debug,
realm,
rumAccessToken,
...rrwebConf
} = config;
tracer = trace.getTracer("splunk.rr-web", VERSION);
const span = tracer.startSpan("record init");

// Check if sampler is ignoring this
if (!span.isRecording()) {
Expand All @@ -141,18 +162,22 @@ const SplunkRumRecorder = {
if (!exportUrl) {
exportUrl = `https://rum-ingest.${realm}.signalfx.com/v1/rumreplay`;
} else {
console.warn('Splunk Session Recorder: Realm value ignored (beaconEndpoint has been specified)');
console.warn(
"Splunk Session Recorder: Realm value ignored (beaconEndpoint has been specified)"
);
}
}

if (!exportUrl) {
console.error('Session recorder could not determine `exportUrl`, please ensure that `realm` or `beaconEndpoint` is specified and try again');
console.error(
"Session recorder could not determine `exportUrl`, please ensure that `realm` or `beaconEndpoint` is specified and try again"
);
return;
}

const headers = {};
if (apiToken) {
headers['X-SF-Token'] = apiToken;
headers["authorization"] = apiToken;
}

if (rumAccessToken) {
Expand All @@ -162,13 +187,13 @@ const SplunkRumRecorder = {
const exporter = new OTLPLogExporter({
beaconUrl: exportUrl,
debug,
headers,
headers,
getResourceAttributes() {
return {
...resource.attributes,
'splunk.rumSessionId': SplunkRum.getSessionId()!
"splunk.rumSessionId": SplunkRum.getSessionId()!,
};
}
},
});
const processor = new BatchLogProcessor(exporter, {});

Expand All @@ -177,7 +202,7 @@ const SplunkRumRecorder = {

inited = record({
maskAllInputs: true,
maskTextSelector: '*',
maskTextSelector: "*",
...rrwebConf,
emit(event) {
if (paused) {
Expand Down Expand Up @@ -214,16 +239,12 @@ const SplunkRumRecorder = {
for (let i = 0; i < totalC; i++) {
const start = i * MAX_CHUNK_SIZE;
const end = (i + 1) * MAX_CHUNK_SIZE;
const log = convert(
decoder.decode(body.slice(start, end)),
time,
{
'rr-web.offset': logCounter++,
'rr-web.event': eventI,
'rr-web.chunk': i + 1,
'rr-web.total-chunks': totalC
}
);
const log = convert(decoder.decode(body.slice(start, end)), time, {
"rr-web.offset": logCounter++,
"rr-web.event": eventI,
"rr-web.chunk": i + 1,
"rr-web.total-chunks": totalC,
});
if (debug) {
console.log(log);
}
Expand All @@ -241,7 +262,7 @@ const SplunkRumRecorder = {
paused = false;
if (!oldPaused) {
record.takeFullSnapshot();
tracer.startSpan('record resume').end();
tracer.startSpan("record resume").end();
}
},
stop(): void {
Expand All @@ -250,7 +271,7 @@ const SplunkRumRecorder = {
}

if (paused) {
tracer.startSpan('record stop').end();
tracer.startSpan("record stop").end();
}

paused = true;
Expand Down
48 changes: 36 additions & 12 deletions packages/web/src/exporters/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,56 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { Attributes } from '@opentelemetry/api';
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import { Attributes } from "@opentelemetry/api";
import { ReadableSpan } from "@opentelemetry/sdk-trace-base";

export interface SplunkExporterConfig {
url: string;
onAttributesSerializing?: (attributes: Attributes, span: ReadableSpan) => Attributes,
xhrSender?: (url: string, data: string, headers?: Record<string, string>) => void,
beaconSender?: (url: string, data: string, headers?: Record<string, string>) => void,
onAttributesSerializing?: (
attributes: Attributes,
span: ReadableSpan
) => Attributes;
xhrSender?: (
url: string,
data: string,
headers?: Record<string, string>
) => void;
beaconSender?: (
url: string,
data: string,
headers?: Record<string, string>
) => void;
apiToken?: string;
}

export function NOOP_ATTRIBUTES_TRANSFORMER(attributes: Attributes): Attributes {
export function NOOP_ATTRIBUTES_TRANSFORMER(
attributes: Attributes
): Attributes {
return attributes;
}
export function NATIVE_XHR_SENDER(url: string, data: string, headers?: Record<string, string>): void {
export function NATIVE_XHR_SENDER(
url: string,
data: string,
headers?: Record<string, string>
): void {
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.open("POST", url);
const defaultHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
Accept: "application/json",
"Content-Type": "application/json",
};
Object.entries(Object.assign(Object.assign({}, defaultHeaders), headers)).forEach(([k, v]) => {
Object.entries(
Object.assign(Object.assign({}, defaultHeaders), headers)
).forEach(([k, v]) => {
xhr.setRequestHeader(k, v);
});
xhr.send(data);
}
export function NATIVE_BEACON_SENDER(url: string, data: string, blobPropertyBag?: BlobPropertyBag): void {
export function NATIVE_BEACON_SENDER(
url: string,
data: string,
blobPropertyBag?: BlobPropertyBag
): void {
const payload = blobPropertyBag ? new Blob([data], blobPropertyBag) : data;
navigator.sendBeacon(url, payload);
}
54 changes: 33 additions & 21 deletions packages/web/src/exporters/otlp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,69 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { diag } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { NOOP_ATTRIBUTES_TRANSFORMER, NATIVE_XHR_SENDER, NATIVE_BEACON_SENDER, SplunkExporterConfig } from './common';
import { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer';
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import { diag } from "@opentelemetry/api";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import {
NOOP_ATTRIBUTES_TRANSFORMER,
NATIVE_XHR_SENDER,
NATIVE_BEACON_SENDER,
SplunkExporterConfig,
} from "./common";
import { IExportTraceServiceRequest } from "@opentelemetry/otlp-transformer";
import { ReadableSpan } from "@opentelemetry/sdk-trace-base";

export class SplunkOTLPTraceExporter extends OTLPTraceExporter {
protected readonly _onAttributesSerializing: SplunkExporterConfig['onAttributesSerializing'];
protected readonly _xhrSender: SplunkExporterConfig['xhrSender'] = NATIVE_XHR_SENDER;
protected readonly _beaconSender: SplunkExporterConfig['beaconSender'] = typeof navigator !== 'undefined' && navigator.sendBeacon ? NATIVE_BEACON_SENDER : undefined;
protected readonly _onAttributesSerializing: SplunkExporterConfig["onAttributesSerializing"];
protected readonly _xhrSender: SplunkExporterConfig["xhrSender"] =
NATIVE_XHR_SENDER;
protected readonly _beaconSender: SplunkExporterConfig["beaconSender"] =
typeof navigator !== "undefined" && navigator.sendBeacon
? NATIVE_BEACON_SENDER
: undefined;
private readonly apiToken: string;

constructor(options: SplunkExporterConfig) {
super(options);
this._onAttributesSerializing = options.onAttributesSerializing || NOOP_ATTRIBUTES_TRANSFORMER;
this._onAttributesSerializing =
options.onAttributesSerializing || NOOP_ATTRIBUTES_TRANSFORMER;
this.apiToken = options.apiToken as string;
}

convert(spans: ReadableSpan[]): IExportTraceServiceRequest {
// Changes: Add attribute serializing hook to remove data before export
spans = spans.map(span => {
spans = spans.map((span) => {
// @ts-expect-error Yep we're overwriting a readonly property here. Deal with it
span.attributes = this._onAttributesSerializing ? this._onAttributesSerializing(span.attributes, span) : span.attributes;
span.attributes = this._onAttributesSerializing
? this._onAttributesSerializing(span.attributes, span)
: span.attributes;
return span;
});

return super.convert(spans);
}

send(
items: ReadableSpan[],
onSuccess: () => void,
): void {
send(items: ReadableSpan[], onSuccess: () => void): void {
if (this._shutdownOnce.isCalled) {
diag.debug('Shutdown already started. Cannot send objects');
diag.debug("Shutdown already started. Cannot send objects");
return;
}
const serviceRequest = this.convert(items);
const body = JSON.stringify(serviceRequest);

// Changed: Determine which exporter to use at the time of export
if (document.hidden && this._beaconSender && body.length <= 64000) {
this._beaconSender(this.url, body, { type: 'application/json' });
this._beaconSender(this.url, body, { type: "application/json" });
} else {
this._xhrSender!(this.url, body, {
// These headers may only be necessary for otel's collector,
// need to test with actual ingest
Accept: 'application/json',
'Content-Type': 'application/json',
...this.headers
Accept: "application/json",
"Content-Type": "application/json",
authorization: this.apiToken,
...this.headers,
});
}

onSuccess();
}
}
}
Loading

0 comments on commit c8e3c81

Please sign in to comment.