@ogcio/o11y-sdk-react 0.1.0-beta.2
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/CHANGELOG.md +8 -0
- package/index.ts +10 -0
- package/lib/index.ts +48 -0
- package/lib/instrumentation.faro.ts +84 -0
- package/package.json +44 -0
- package/test/index.test.ts +28 -0
- package/test/validation.test.ts +65 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +26 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0-beta.2](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-react@v0.1.0-beta.1...@ogcio/o11y-sdk-react@v0.1.0-beta.2) (2025-01-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* o11y sdk repo setup ([#15](https://github.com/ogcio/o11y/issues/15)) ([c5816ba](https://github.com/ogcio/o11y/commit/c5816baff1454353f12539949959e84964ed6401))
|
package/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Faro,
|
|
3
|
+
withFaroErrorBoundary,
|
|
4
|
+
withFaroProfiler,
|
|
5
|
+
} from "@grafana/faro-react";
|
|
6
|
+
import buildFaroInstrumentation from "./lib/instrumentation.faro.js";
|
|
7
|
+
|
|
8
|
+
export * from "./lib/index.js";
|
|
9
|
+
export { withFaroErrorBoundary, withFaroProfiler, Faro };
|
|
10
|
+
export { buildFaroInstrumentation as instrumentFaro };
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
interface SDKConfig {
|
|
2
|
+
/**
|
|
3
|
+
* The opentelemetry collector entrypoint GRPC url.
|
|
4
|
+
* If the collectoUrl is null or undefined, the instrumentation will not be activated.
|
|
5
|
+
* @example http://alloy:4317
|
|
6
|
+
*/
|
|
7
|
+
collectorUrl: string;
|
|
8
|
+
/**
|
|
9
|
+
* Name of your application used for the collector to group logs
|
|
10
|
+
*/
|
|
11
|
+
serviceName?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Diagnostic log level for the internal runtime instrumentation
|
|
14
|
+
*
|
|
15
|
+
* @type string
|
|
16
|
+
* @default INFO
|
|
17
|
+
*/
|
|
18
|
+
diagLogLevel?: SDKLogLevel;
|
|
19
|
+
/**
|
|
20
|
+
* Collector signals processing mode.
|
|
21
|
+
* signle: makes an http/grpc request for each signal and it is immediately processed inside grafana
|
|
22
|
+
* batch: sends multiple signals within a time window, optimized to reduce http/grpc calls in production
|
|
23
|
+
*
|
|
24
|
+
* @type string
|
|
25
|
+
* @default batch
|
|
26
|
+
*/
|
|
27
|
+
collectorMode?: SDKCollectorMode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FaroSDKConfig extends SDKConfig {
|
|
31
|
+
/**
|
|
32
|
+
* String with single or multiple URLs separated by comma used by the instrumentation to ignore CORS errors and send signals through it
|
|
33
|
+
* @example http://localhost:9000,http://localhost:8000
|
|
34
|
+
* @example http://localhost:9000
|
|
35
|
+
*/
|
|
36
|
+
corsTraceHeaders?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SDKCollectorMode = "single" | "batch";
|
|
40
|
+
|
|
41
|
+
export type SDKLogLevel =
|
|
42
|
+
| "NONE"
|
|
43
|
+
| "ERROR"
|
|
44
|
+
| "WARN"
|
|
45
|
+
| "INFO"
|
|
46
|
+
| "DEBUG"
|
|
47
|
+
| "VERBOSE"
|
|
48
|
+
| "ALL";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { FaroSDKConfig } from "./index.js";
|
|
2
|
+
import {
|
|
3
|
+
ErrorsInstrumentation,
|
|
4
|
+
getWebInstrumentations,
|
|
5
|
+
initializeFaro,
|
|
6
|
+
WebVitalsInstrumentation,
|
|
7
|
+
type Faro,
|
|
8
|
+
} from "@grafana/faro-react";
|
|
9
|
+
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
10
|
+
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
11
|
+
import isUrl from "is-url";
|
|
12
|
+
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
|
|
13
|
+
import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-user-interaction";
|
|
14
|
+
import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load";
|
|
15
|
+
import { TracingInstrumentation } from "@grafana/faro-web-tracing";
|
|
16
|
+
|
|
17
|
+
export default function buildFaroInstrumentation(
|
|
18
|
+
config?: FaroSDKConfig,
|
|
19
|
+
): Faro | undefined {
|
|
20
|
+
if (!config) {
|
|
21
|
+
console.warn(
|
|
22
|
+
"observability config not set. Skipping Faro OpenTelemetry instrumentation.",
|
|
23
|
+
);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!config.collectorUrl) {
|
|
28
|
+
console.warn(
|
|
29
|
+
"collectorUrl not set. Skipping Faro OpenTelemetry instrumentation.",
|
|
30
|
+
);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isUrl(config.collectorUrl)) {
|
|
35
|
+
console.error(
|
|
36
|
+
"collectorUrl does not use a valid format. Skipping Faro OpenTelemetry instrumentation.",
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
diag.setLogger(
|
|
43
|
+
new DiagConsoleLogger(),
|
|
44
|
+
config.diagLogLevel
|
|
45
|
+
? DiagLogLevel[config.diagLogLevel]
|
|
46
|
+
: DiagLogLevel.INFO,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const faro = initializeFaro({
|
|
50
|
+
url: config.collectorUrl,
|
|
51
|
+
app: {
|
|
52
|
+
name: config.serviceName,
|
|
53
|
+
},
|
|
54
|
+
batching: {
|
|
55
|
+
enabled: !(config.collectorMode === "single"),
|
|
56
|
+
},
|
|
57
|
+
instrumentations: [
|
|
58
|
+
...getWebInstrumentations({
|
|
59
|
+
captureConsole: true,
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
new ErrorsInstrumentation(),
|
|
63
|
+
new WebVitalsInstrumentation(),
|
|
64
|
+
new TracingInstrumentation({
|
|
65
|
+
propagator: new W3CTraceContextPropagator(),
|
|
66
|
+
instrumentations: [
|
|
67
|
+
new DocumentLoadInstrumentation(),
|
|
68
|
+
new FetchInstrumentation({
|
|
69
|
+
ignoreUrls: [config.collectorUrl], // ignore collector to avoid fetch loop
|
|
70
|
+
propagateTraceHeaderCorsUrls: config.corsTraceHeaders
|
|
71
|
+
? config.corsTraceHeaders.split(",").map((s) => new RegExp(s))
|
|
72
|
+
: undefined,
|
|
73
|
+
}),
|
|
74
|
+
new UserInteractionInstrumentation(),
|
|
75
|
+
],
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
console.log("Faro OpenTelemetry instrumentation started successfully.");
|
|
80
|
+
return faro;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("Error starting Faro OpenTelemetry instrumentation:", error);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ogcio/o11y-sdk-react",
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
|
+
"description": "Opentelemetry standard instrumentation SDK for React based project",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./*": "./dist/*.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"observability",
|
|
13
|
+
"o11y",
|
|
14
|
+
"opentelemetry",
|
|
15
|
+
"faro",
|
|
16
|
+
"react",
|
|
17
|
+
"ogcio"
|
|
18
|
+
],
|
|
19
|
+
"author": "team:ogcio/observability",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@grafana/faro-react": "^1.12.3",
|
|
23
|
+
"@grafana/faro-web-sdk": "^1.12.3",
|
|
24
|
+
"@grafana/faro-web-tracing": "^1.12.3",
|
|
25
|
+
"@opentelemetry/api": "^1.9.0",
|
|
26
|
+
"@opentelemetry/core": "1.30.1",
|
|
27
|
+
"@opentelemetry/instrumentation-document-load": "^0.44.0",
|
|
28
|
+
"@opentelemetry/instrumentation-fetch": "^0.57.1",
|
|
29
|
+
"@opentelemetry/instrumentation-user-interaction": "^0.44.0",
|
|
30
|
+
"is-url": "^1.2.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/is-url": "^1.2.32",
|
|
34
|
+
"@types/node": "^22.12.0",
|
|
35
|
+
"@vitest/coverage-v8": "^3.0.4",
|
|
36
|
+
"tsx": "^4.19.2",
|
|
37
|
+
"typescript": "^5.7.3",
|
|
38
|
+
"vitest": "^3.0.4"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
42
|
+
"test": "vitest"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { FaroSDKConfig } from "../lib";
|
|
3
|
+
import { instrumentFaro } from "../index";
|
|
4
|
+
|
|
5
|
+
vi.mock("../lib/instrumentation.faro", () => ({
|
|
6
|
+
default: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("instrumentFaro", () => {
|
|
10
|
+
test("should call buildFaroInstrumentation with the provided config", async () => {
|
|
11
|
+
const config: FaroSDKConfig = {
|
|
12
|
+
serviceName: "custom-service",
|
|
13
|
+
collectorUrl: "http://custom-collector.com",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const buildFaroInstrumentation = await import(
|
|
17
|
+
"../lib/instrumentation.faro"
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
instrumentFaro(config);
|
|
21
|
+
|
|
22
|
+
expect(buildFaroInstrumentation.default).toHaveBeenCalledWith(config);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("should not throw when called without arguments", () => {
|
|
26
|
+
expect(() => instrumentFaro()).not.toThrow();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { test, describe, assert, vi, expect } from "vitest";
|
|
2
|
+
import buildFaroInstrumentation from "../lib/instrumentation.faro.js";
|
|
3
|
+
import { Faro } from "@grafana/faro-react";
|
|
4
|
+
import { instrumentFaro } from "../index.js";
|
|
5
|
+
|
|
6
|
+
describe("validation config: should return without breaking the execution", () => {
|
|
7
|
+
test("should call buildFaroInstrumentation without config and skip instrumentation", async () => {
|
|
8
|
+
const consoleWarnSpy = vi
|
|
9
|
+
.spyOn(console, "warn")
|
|
10
|
+
.mockImplementation(vi.fn());
|
|
11
|
+
|
|
12
|
+
instrumentFaro();
|
|
13
|
+
|
|
14
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
15
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
16
|
+
"observability config not set. Skipping Faro OpenTelemetry instrumentation.",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("faro instrumentation: url undefined", () => {
|
|
21
|
+
let sdk: Faro | undefined = undefined;
|
|
22
|
+
|
|
23
|
+
const consoleWarnSpy = vi
|
|
24
|
+
.spyOn(console, "warn")
|
|
25
|
+
.mockImplementation(vi.fn());
|
|
26
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(vi.fn());
|
|
27
|
+
|
|
28
|
+
assert.doesNotThrow(() => {
|
|
29
|
+
sdk = buildFaroInstrumentation({
|
|
30
|
+
collectorUrl: undefined!,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
assert.equal(sdk, undefined);
|
|
35
|
+
|
|
36
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
37
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
38
|
+
"collectorUrl not set. Skipping Faro OpenTelemetry instrumentation.",
|
|
39
|
+
);
|
|
40
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("faro instrumentation: invalid url", () => {
|
|
44
|
+
let sdk: Faro | undefined = undefined;
|
|
45
|
+
|
|
46
|
+
const consoleErrorSpy = vi
|
|
47
|
+
.spyOn(console, "error")
|
|
48
|
+
.mockImplementation(vi.fn());
|
|
49
|
+
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(vi.fn());
|
|
50
|
+
|
|
51
|
+
assert.doesNotThrow(() => {
|
|
52
|
+
sdk = buildFaroInstrumentation({
|
|
53
|
+
collectorUrl: "notavalidURL",
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
assert.equal(sdk, undefined);
|
|
58
|
+
|
|
59
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
60
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
61
|
+
"collectorUrl does not use a valid format. Skipping Faro OpenTelemetry instrumentation.",
|
|
62
|
+
);
|
|
63
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
|
4
|
+
"module": "NodeNext" /* Specify what module code is generated. */,
|
|
5
|
+
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
|
8
|
+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
|
9
|
+
"strict": true /* Enable all strict type-checking options. */,
|
|
10
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"exclude": ["**/test/**"]
|
|
14
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
watch: false,
|
|
7
|
+
include: ["**/test/*.test.ts"],
|
|
8
|
+
exclude: ["**/fixtures/**", "**/dist/**"],
|
|
9
|
+
poolOptions: {
|
|
10
|
+
threads: {
|
|
11
|
+
maxThreads: 8,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
clearMocks: true,
|
|
15
|
+
testTimeout: 30_000,
|
|
16
|
+
coverage: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
provider: "v8",
|
|
19
|
+
reportsDirectory: "coverage",
|
|
20
|
+
reporter: ["lcov", "cobertura"],
|
|
21
|
+
clean: true,
|
|
22
|
+
},
|
|
23
|
+
reporters: ["default", ["junit", { outputFile: "test-report.xml" }]],
|
|
24
|
+
environment: "node",
|
|
25
|
+
},
|
|
26
|
+
});
|