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 new packages for RN #2

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Changes from 6 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4630be4
feat: add new packages for RN
ilia-kurganskii Dec 20, 2024
e2f8fe2
fix: remove deps
ilia-kurganskii Dec 20, 2024
efc8ebb
fix: ignore browser sdk if flag enabled
ilia-kurganskii Dec 20, 2024
90358b9
fix: fix related bundles
ilia-kurganskii Dec 21, 2024
6745ef6
feat: add initial `instrumentation-websocket` as experimental
lucasbento Dec 21, 2024
b366bc4
Merge branch 'feat/add-react-native-support' of https://github.com/bi…
lucasbento Dec 21, 2024
b008d43
feat: init react navigation
marcocaldera Dec 21, 2024
d26abdf
feat: add WS message duration tracking
lucasbento Dec 23, 2024
1615683
fix: instrumentation navigation
marcocaldera Dec 23, 2024
744f3a1
Merge remote-tracking branch 'origin/feat/add-react-native-support' i…
marcocaldera Dec 23, 2024
4dfeaf2
feat: improve code and add span
marcocaldera Dec 24, 2024
02005ea
feat: improve code and add span
marcocaldera Dec 24, 2024
406c930
fix: undefined `ErrorEvent` in case of a WS error
lucasbento Dec 25, 2024
4ae14e2
feat: add react-saga and axios packages
ilia-kurganskii Dec 25, 2024
911a288
feat: add app startup instrumentation
lucasbento Dec 25, 2024
aadcff5
Merge branch 'feat/add-react-native-support' of https://github.com/bi…
lucasbento Dec 25, 2024
6b9b359
feat: add tracing for takeLatest
ilia-kurganskii Dec 26, 2024
bcfa527
refactor: add `messageTransform`
lucasbento Dec 26, 2024
c4097c8
Merge branch 'feat/add-react-native-support' of https://github.com/bi…
lucasbento Dec 26, 2024
3862f83
feat: add propagation context to call
ilia-kurganskii Dec 26, 2024
61e99f2
feat: update logs for instumentations
ilia-kurganskii Dec 30, 2024
5a98193
feat: add axios handler for span
ilia-kurganskii Dec 30, 2024
de38a9f
feat: add initial route change
marcocaldera Dec 31, 2024
3f27496
Merge remote-tracking branch 'origin/feat/add-react-native-support' i…
marcocaldera Dec 31, 2024
364acf1
feat: add `sendTransform` option to `instrumentation-websocket`
lucasbento Jan 2, 2025
d5f4bc6
fix: missing class methods
lucasbento Jan 2, 2025
448c807
Merge branch 'feat/add-react-native-support' of https://github.com/bi…
lucasbento Jan 2, 2025
fdab13c
fix: fix build issue
ilia-kurganskii Jan 2, 2025
ec6c4d2
fix: fix build issue
ilia-kurganskii Jan 2, 2025
cd0ac84
fix: fix build issue
ilia-kurganskii Jan 2, 2025
0f15391
fix: fix build issue
ilia-kurganskii Jan 2, 2025
ff9ec96
fix: fix build issue
ilia-kurganskii Jan 2, 2025
8f5b153
Revert "fix: fix build issue"
ilia-kurganskii Jan 3, 2025
56b8416
refactor: change from milliseconds to seconds
lucasbento Jan 4, 2025
0f6e372
refactor: change from milliseconds to seconds
lucasbento Jan 5, 2025
f5060fc
fix: use correct values for `browser` meta
lucasbento Jan 6, 2025
24d3895
refactor: remove unneeded param
lucasbento Jan 6, 2025
c66f2cf
fix: logger
marcocaldera Jan 7, 2025
23599dd
Merge branch 'feat/add-react-native-support' of github.com:bitvavo/fa…
marcocaldera Jan 7, 2025
8d9b5e8
refactor: switch `console.error` to `console.warn`
lucasbento Jan 7, 2025
d74230a
refactor: move import from `faro-web-sdk`
lucasbento Jan 7, 2025
fd9a135
fix: writable map
marcocaldera Jan 13, 2025
db2eead
refactor: add a better way to measure startup time
lucasbento Jan 30, 2025
761df19
refactor: remove logs
lucasbento Jan 30, 2025
8438b12
refactor: add missing `do-catch`
lucasbento Jan 30, 2025
3face70
Merge pull request #4 from bitvavo/refactor/better-statup-time-measure
lucasbento Jan 31, 2025
a72bdc0
refactor: add missing name variable for logs
lucasbento Jan 31, 2025
5e7262a
feat: add `whiteListedURLs` to web-socket instrumentation config
lucasbento Feb 6, 2025
2aa2688
refactor: initialize `whiteListedURLs` with empty array
lucasbento Feb 7, 2025
1a2882e
feat: add support for persistent sessions (#5)
ilia-kurganskii Feb 7, 2025
185d655
refactor: change startup duration to submit as seconds
lucasbento Feb 10, 2025
5615ba3
Merge branch 'feat/add-react-native-support' of https://github.com/bi…
lucasbento Feb 10, 2025
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
1 change: 1 addition & 0 deletions experimental/instrumentation-otel-axios/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
supports es6-module
52 changes: 52 additions & 0 deletions experimental/instrumentation-otel-axios/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @grafana/instrumentation-fetch

Faro instrumentation of the JavaScript [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API.

❗️*Warning*: this package is experimental and may be subject to frequent and breaking changes.
Use at your own risk.❗️

## Installation and Usage

❗️*Warning*: This package is not interoperable with `@opentelemetry/instrumentation-fetch`.
Use one or the other❗️

Add the instrumentation as outlined below.
The instrumentation send the following events alongside respective request/response data like HTTP
headers and other response properties like status codes the requests url and more.

Event names are:

- `faro.fetch.resolved` for resolved requests.
- `faro.fetch.rejected` for rejected requests.

```ts
// index.ts
import { FetchInstrumentation } from '@grafana/faro-instrumentation-fetch';
import { getWebInstrumentations, initializeFaro } from '@grafana/faro-react';

initializeFaro({
// ...
instrumentations: [
// Load the default Web instrumentations
...getWebInstrumentations(),
// Add fetch instrumentation
new FetchInstrumentation(),
],
});


// myApi.ts
fetch(...) // Use fetch as normal - telemetry data is sent to your Faro endpoint
```

## Backend correlation

In order to prepare backend correlation, this instrumentation adds the following headers to each
request that server-side instrumentation can use as context:

- `x-faro-session` - the client-side session id

## Planned Development

- Additional functionality to correlate frontend requests with backend actions
- Event attributes with end-to-end timing details
16 changes: 16 additions & 0 deletions experimental/instrumentation-otel-axios/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { jestBaseConfig } = require('../../jest.config.base.js');

module.exports = {
...jestBaseConfig,
roots: ['experimental/instrumentation-fetch/src'],
testEnvironment: 'jsdom',
setupFiles: ['<rootDir>/experimental/instrumentation-fetch/src/setupTests.ts'],
moduleNameMapper: {
'^@remix-run/router$': '<rootDir>/index.ts',
'^@remix-run/web-blob$': require.resolve('@remix-run/web-blob'),
'^@remix-run/web-fetch$': require.resolve('@remix-run/web-fetch'),
'^@remix-run/web-form-data$': require.resolve('@remix-run/web-form-data'),
'^@remix-run/web-stream$': require.resolve('@remix-run/web-stream'),
'^@web3-storage/multipart-parser$': require.resolve('@web3-storage/multipart-parser'),
},
};
66 changes: 66 additions & 0 deletions experimental/instrumentation-otel-axios/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@grafana/faro-instrumentation-otel-axios",
"version": "1.12.2",
"description": "Faro axios open telemetry auto-instrumentation package",
"keywords": [
"observability",
"apm",
"rum",
"logs",
"traces",
"metrics",
"fetch"
],
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"scripts": {
"test": "jest",
"start": "yarn watch",
"build": "run-s build:*",
"build:compile": "run-p build:compile:*",
"build:compile:cjs": "tsc --build tsconfig.cjs.json",
"build:compile:esm": "tsc --build tsconfig.esm.json",
"build:compile:bundle": "run-s build:compile:bundle:*",
"build:compile:bundle:create": "rollup -c ./rollup.config.js",
"build:compile:bundle:remove-extras": "rimraf dist/bundle/dist",
"watch": "run-s watch:compile",
"watch:compile": "yarn build:compile:cjs -w",
"clean": "rimraf dist/ yarn-error.log",
"quality": "run-s quality:*",
"quality:test": "jest",
"quality:format": "prettier --cache --cache-location=../../.cache/prettier/instrumentationOtelAxios --ignore-path ../../.prettierignore -w \"./**/*.{js,jsx,ts,tsx,css,scss,md,yaml,yml,json}\"",
"quality:lint": "run-s quality:lint:*",
"quality:lint:eslint": "eslint --cache --cache-location ../../.cache/eslint/instrumentationOtelAxios --ignore-path ../../.eslintignore \"./**/*.{js,jsx,ts,tsx}\"",
"quality:lint:prettier": "prettier --cache --cache-location=../../.cache/prettier/instrumentationOtelAxios --ignore-path ../../.prettierignore -c \"./**/*.{js,jsx,ts,tsx,css,scss,md,yaml,yml,json}\"",
"quality:lint:md": "markdownlint README.md",
"quality:circular-deps": "madge --circular ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/grafana/faro-web-sdk.git",
"directory": "experimental/instrumentation-fetch"
},
"author": "Grafana Labs",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/grafana/faro-web-sdk/issues"
},
"homepage": "https://github.com/grafana/faro-web-sdk",
"dependencies": {
"@grafana/faro-core": "^1.12.2",
"@opentelemetry/api": "^1.9.0"
},
"peerDependencies": {
"axios": "*"
},
"publishConfig": {
"access": "public"
},
"files": [
".browserslistrc",
"dist",
"README.md",
"LICENSE"
]
}
3 changes: 3 additions & 0 deletions experimental/instrumentation-otel-axios/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { getRollupConfigBase } = require('../../rollup.config.base.js');

module.exports = getRollupConfigBase('instrumentationOtelAxios');
5 changes: 5 additions & 0 deletions experimental/instrumentation-otel-axios/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum AttributeNames {
COMPONENT = 'component',
HTTP_ERROR_NAME = 'http.error_name',
HTTP_STATUS_TEXT = 'http.status_text'
}
1 change: 1 addition & 0 deletions experimental/instrumentation-otel-axios/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AxiosInstrumentation } from './instrumentation';
169 changes: 169 additions & 0 deletions experimental/instrumentation-otel-axios/src/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import * as api from '@opentelemetry/api';
import * as core from '@opentelemetry/core';
import { InstrumentationBase, isWrapped } from '@opentelemetry/instrumentation';
import * as web from '@opentelemetry/sdk-trace-web';
import {
SEMATTRS_HTTP_HOST,
SEMATTRS_HTTP_METHOD,
SEMATTRS_HTTP_SCHEME,
SEMATTRS_HTTP_STATUS_CODE,
SEMATTRS_HTTP_URL,
SEMATTRS_HTTP_USER_AGENT
} from '@opentelemetry/semantic-conventions';
import { Axios, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

import {AttributeNames} from "./constants";
import type {AxiosInstrumentationOptions} from "./types";

/**
* Additional custom attribute names
*/


export const VERSION = '1.12.3';

/**
* Configuration interface for the AxiosInstrumentation
*/


export class AxiosInstrumentation extends InstrumentationBase<AxiosInstrumentationOptions> {
readonly component: string = 'axios';

readonly version: string = VERSION;

moduleName = this.component;

constructor(config: AxiosInstrumentationOptions = {}) {
super('@grafana/instrumentation-axios', VERSION, config);
this.enable = this.enable.bind(this);
this.disable = this.disable.bind(this);
}

init(): void {
// no-op
}

private _addFinalSpanAttributes(span: api.Span, response: AxiosResponse) {
const responseUrl = `${response.config.baseURL ?? ''}/${response.config.url}`;
const parsedUrl = web.parseUrl(responseUrl);

span.updateName(`${response.config.method?.toUpperCase()}`);
span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, response.status);
if (response.statusText != null) {
span.setAttribute(AttributeNames.HTTP_STATUS_TEXT, response.statusText);
}
span.setAttribute(SEMATTRS_HTTP_HOST, parsedUrl.host);
span.setAttribute(SEMATTRS_HTTP_SCHEME, parsedUrl.protocol.replace(':', ''));
if (typeof navigator !== 'undefined') {
span.setAttribute(SEMATTRS_HTTP_USER_AGENT, navigator.userAgent);
}
}

private _addAxiosHeaders(config: AxiosRequestConfig): void {
const headers: Record<string, unknown> = config.headers || {};
api.propagation.inject(api.context.active(), headers);
// @ts-ignore
config.headers = headers;
}

private createSpan(url: string, options: Partial<Request | RequestInit> = {}): api.Span | undefined {
if (core.isUrlIgnored(url, this.getConfig().ignoreUrls)) {
this._diag.debug('ignoring span as url matches ignored url');
return;
}

const method = (options.method || 'GET').toUpperCase();
const spanName = `HTTP ${method}`;

return this.tracer.startSpan(spanName, {
kind: api.SpanKind.CLIENT,
attributes: {
[AttributeNames.COMPONENT]: this.moduleName,
[SEMATTRS_HTTP_METHOD]: method,
[SEMATTRS_HTTP_URL]: url
}
});
}

private _endSpan<T = any>(span: api.Span, response?: AxiosResponse<T>, error?: Error) {
const endTime = core.millisToHrTime(Date.now());

if (response) {
// It's an AxiosResponse
this._addFinalSpanAttributes(span, response);

if (response.status >= 400) {
span.setStatus({ code: api.SpanStatusCode.ERROR });
} else {
span.setStatus({ code: api.SpanStatusCode.OK });
}
} else if (error) {
// It's an error object
span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
if (error.name) {
span.setAttribute(AttributeNames.HTTP_ERROR_NAME, error.name);
}
}

span.end(endTime);
}

private _patchAxios() {
const plugin = this;

// Wrap axios.request
if (isWrapped(Axios.prototype.request)) {
plugin._unwrap(Axios.prototype, 'request');
}

plugin._wrap(Axios.prototype, 'request', (original: <T = any, R = AxiosResponse<T>, D=any>(config: AxiosRequestConfig<D>) => Promise<R>) => {
return function patchRequest<T = any, R = AxiosResponse<T>, D = any>(this: AxiosInstance, requestConfig: AxiosRequestConfig<D>): Promise<R> {
const url = requestConfig.url ?? '';

// Create a new span if the URL is not ignored
const span = plugin.createSpan(url, { method: requestConfig.method });
if (!span) {
return original.apply(this, [requestConfig]) as Promise<R>;
}

return api.context.with(api.trace.setSpan(api.context.active(), span), () => {
plugin._addAxiosHeaders(requestConfig);

return (original
.apply(this, [requestConfig]) as Promise<R>)
.then((response: R) => {
try {
plugin._endSpan<T>(span, response as AxiosResponse<T>);
} catch (e) {
plugin._diag.error('Error ending span', e);
}
return response;
})
.catch((error: any) => {
plugin._endSpan(
span,
undefined,
error?.response || {
message: error?.message || 'Unknown error',
name: error?.name || 'Error'
}
);
throw error;
});
});
};
});

}

override enable(): void {
// Patch the instance passed in via this._config
this._patchAxios();
}

override disable(): void {
// Unwrap the instance
this._unwrap(Axios.prototype, 'request');
}
}
12 changes: 12 additions & 0 deletions experimental/instrumentation-otel-axios/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { fetch, Request, Response } from '@remix-run/web-fetch';

if (!globalThis.fetch) {
// @ts-expect-error
globalThis.fetch = fetch;

// @ts-expect-error
globalThis.Request = Request;

// @ts-expect-error
globalThis.Response = Response;
}
10 changes: 10 additions & 0 deletions experimental/instrumentation-otel-axios/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {InstrumentationConfig} from "@opentelemetry/instrumentation";
import type * as web from "@opentelemetry/sdk-trace-web";

export interface AxiosInstrumentationOptions extends InstrumentationConfig {
clearTimingResources?: boolean;
propagateTraceHeaderCorsUrls?: web.PropagateTraceHeaderCorsUrls;
ignoreUrls?: Array<string | RegExp>;
ignoreNetworkEvents?: boolean;
measureRequestSize?: boolean;
}
12 changes: 12 additions & 0 deletions experimental/instrumentation-otel-axios/tsconfig.cjs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.cjs.json",
"compilerOptions": {
"declarationDir": "./dist/types",
"outDir": "./dist/cjs",
"rootDir": "./src",
"tsBuildInfoFile": "../../.cache/tsc/instrumentationOtelAxios.cjs.tsbuildinfo"
},
"include": ["./src"],
"exclude": ["**/*.test.ts"],
"references": [{ "path": "../../packages/core/tsconfig.spec.json" }]
}
12 changes: 12 additions & 0 deletions experimental/instrumentation-otel-axios/tsconfig.esm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.esm.json",
"compilerOptions": {
"declarationDir": "./dist/types",
"outDir": "./dist/esm",
"rootDir": "./src",
"tsBuildInfoFile": "../../.cache/tsc/instrumentationOtelAxios.esm.tsbuildinfo"
},
"include": ["./src"],
"exclude": ["**/*.test.ts"],
"references": [{ "path": "../../packages/core/tsconfig.spec.json" }]
}
10 changes: 10 additions & 0 deletions experimental/instrumentation-otel-axios/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"noPropertyAccessFromIndexSignature": false
},
"references": [
{ "path": "./tsconfig.cjs.json" },
{ "path": "./tsconfig.esm.json" },
{ "path": "./tsconfig.spec.json" }
]
}
11 changes: 11 additions & 0 deletions experimental/instrumentation-otel-axios/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.spec.json",
"compilerOptions": {
"declarationDir": "./dist/types",
"outDir": "./dist/spec",
"rootDir": "../../",
"tsBuildInfoFile": "../../.cache/tsc/instrumentationOtelAxios.spec.tsbuildinfo"
},
"include": ["./src"],
"references": [{ "path": "../../packages/core/tsconfig.spec.json" }]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
supports es6-module
Loading