diff --git a/backend/compose.yaml b/backend/compose.yaml
index d8ec121..f24f26e 100644
--- a/backend/compose.yaml
+++ b/backend/compose.yaml
@@ -28,7 +28,9 @@ services:
restart: always
grafana:
- image: grafana/otel-lgtm
+ build:
+ context: .
+ dockerfile: grafana.Dockerfile
ports:
- '3000:3000'
- '4317:4317'
diff --git a/backend/grafana.Dockerfile b/backend/grafana.Dockerfile
new file mode 100644
index 0000000..fe99abd
--- /dev/null
+++ b/backend/grafana.Dockerfile
@@ -0,0 +1,5 @@
+FROM grafana/otel-lgtm
+
+COPY tempo-config.yaml .
+
+CMD /otel-lgtm/run-all.sh
\ No newline at end of file
diff --git a/backend/tempo-config.yaml b/backend/tempo-config.yaml
new file mode 100644
index 0000000..032f04e
--- /dev/null
+++ b/backend/tempo-config.yaml
@@ -0,0 +1,44 @@
+server:
+ http_listen_port: 3200
+ grpc_listen_port: 9096
+
+distributor:
+ receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: "localhost:4417"
+ http:
+ endpoint: "localhost:4418"
+
+storage:
+ trace:
+ backend: local
+ wal:
+ path: /tmp/tempo/wal
+ local:
+ path: /tmp/tempo/blocks
+
+metrics_generator:
+ registry:
+ # A list of labels that will be added to all generated metrics.
+ external_labels:
+ source: tempo
+ processor:
+ span_metrics:
+ enable_target_info: true
+ dimensions: ['react_client.cold_start', 'react_client.page_name', 'react_client.product_id']
+ local_blocks:
+ filter_server_spans: false
+ traces_storage:
+ path: /tmp/tempo/generator/traces
+ storage:
+ path: /tmp/tempo/generator/wal
+ remote_write:
+ - url: http://localhost:9090/api/v1/write
+ send_exemplars: true
+
+overrides:
+ defaults:
+ metrics_generator:
+ processors: [service-graphs, span-metrics, local-blocks]
diff --git a/frontend/index.html b/frontend/index.html
index 7dcdc5f..851195c 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -5,6 +5,9 @@
Vite + React + TS
+
diff --git a/frontend/package.json b/frontend/package.json
index 54732a4..5b41217 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,7 +12,6 @@
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.52.1",
- "@opentelemetry/exporter-prometheus": "^0.52.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.52.1",
"@opentelemetry/instrumentation": "^0.52.1",
"@opentelemetry/instrumentation-document-load": "^0.39.0",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 468bbf1..a488e26 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,15 +1,18 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import ProductDetails from "./pages/ProductDetails";
+import SpansProvider from "./components/SpansProvider/SpansProvider";
const App = () => {
return (
-
-
- } />
- } />
-
-
+
+
+
+ } />
+ } />
+
+
+
);
};
diff --git a/frontend/src/components/Page/Page.tsx b/frontend/src/components/Page/Page.tsx
new file mode 100644
index 0000000..7be8324
--- /dev/null
+++ b/frontend/src/components/Page/Page.tsx
@@ -0,0 +1,56 @@
+import { FC, PropsWithChildren, useEffect, useRef } from "react";
+import { Attributes } from "@opentelemetry/api";
+import useTrackPageView from "../../hooks/useTrackPageView";
+import useSpansContext from "../SpansProvider/hooks/useSpansContext";
+
+type PageProps = {
+ isLoading?: boolean;
+ instrumentation: {
+ pageName: string;
+ attributes?: Attributes;
+ };
+};
+
+const Page: FC> = ({
+ children,
+ instrumentation,
+ isLoading,
+}) => {
+ const wasColdStart = useRef(window.coldStart);
+
+ const { pageName, attributes } = instrumentation;
+ const { endSpan, getOrCreateSpan } = useSpansContext();
+
+ getOrCreateSpan("Page Ready", {
+ startTime: wasColdStart.current
+ ? performance.timeOrigin
+ : new Date().getTime(),
+ attributes: {
+ "react_client.cold_start": wasColdStart.current,
+ "react_client.page_name": pageName,
+ ...attributes,
+ },
+ });
+
+ useEffect(() => {
+ if (window.coldStart) {
+ window.coldStart = false;
+ }
+ }, []);
+
+ useEffect(() => {
+ if (isLoading === undefined || !isLoading) {
+ endSpan("Page Ready");
+ }
+ }, [endSpan, isLoading]);
+
+ useTrackPageView(pageName, attributes);
+
+ if (isLoading) {
+ return "Loading";
+ }
+
+ return <>{children}>;
+};
+
+export default Page;
diff --git a/frontend/src/components/Page/index.ts b/frontend/src/components/Page/index.ts
new file mode 100644
index 0000000..39d0be9
--- /dev/null
+++ b/frontend/src/components/Page/index.ts
@@ -0,0 +1,3 @@
+import Page from "./Page";
+
+export default Page;
diff --git a/frontend/src/components/SpansProvider/SpansProvider.tsx b/frontend/src/components/SpansProvider/SpansProvider.tsx
new file mode 100644
index 0000000..dd929e9
--- /dev/null
+++ b/frontend/src/components/SpansProvider/SpansProvider.tsx
@@ -0,0 +1,51 @@
+import { SpansContext } from "./hooks/useSpansContext";
+import { FC, PropsWithChildren, useCallback, useRef } from "react";
+import { context, Span, SpanOptions, trace } from "@opentelemetry/api";
+import { SpanName } from "./constants/spans";
+
+const SpansProvider: FC = ({ children }) => {
+ const spansRef = useRef