@aurostack/stacks 0.1.1 → 0.2.1
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/README.md +59 -0
- package/cli/src/generate.mjs +13 -1
- package/cli/stack.mjs +1 -1
- package/package.json +1 -1
- package/templates/nest-api/files/.bin/secrets.ts +15 -6
- package/templates/nest-api/files/.env.example +29 -2
- package/templates/nest-api/files/.env.test.example +1 -1
- package/templates/nest-api/files/_gitignore +1 -0
- package/templates/nest-api/files/compose.yml +16 -0
- package/templates/nest-api/files/package.json +18 -3
- package/templates/nest-api/files/src/app.module.ts +9 -1
- package/templates/nest-api/files/src/app.setup.ts +8 -7
- package/templates/nest-api/files/src/common/__tests__/temporal.service.spec.ts +66 -0
- package/templates/nest-api/files/src/common/common.module.ts +4 -3
- package/templates/nest-api/files/src/common/constants/pino.ts +14 -0
- package/templates/nest-api/files/src/common/controllers/index.ts +0 -1
- package/templates/nest-api/files/src/common/modules/index.ts +0 -1
- package/templates/nest-api/files/src/common/modules/logger.module.ts +5 -8
- package/templates/nest-api/files/src/common/services/config.service.ts +43 -5
- package/templates/nest-api/files/src/common/services/index.ts +1 -0
- package/templates/nest-api/files/src/common/services/temporal.service.ts +66 -0
- package/templates/nest-api/files/src/instrumentation.ts +117 -0
- package/templates/nest-api/files/src/main.ts +3 -1
- package/templates/nest-api/files/src/notifications/services/notifications.service.ts +4 -2
- package/templates/nest-api/files/src/telemetry/__tests__/config.spec.ts +67 -0
- package/templates/nest-api/files/src/telemetry/config.ts +62 -0
- package/templates/nest-api/template.json +54 -14
- package/templates/node-worker/files/.bin/secrets.ts +15 -6
- package/templates/node-worker/files/.env.example +27 -0
- package/templates/node-worker/files/Dockerfile +25 -2
- package/templates/node-worker/files/Dockerfile.dev +25 -2
- package/templates/node-worker/files/_gitignore +1 -0
- package/templates/node-worker/files/ecosystem.config.cjs +2 -0
- package/templates/node-worker/files/package.json +24 -4
- package/templates/node-worker/files/src/config.ts +23 -0
- package/templates/node-worker/files/src/index.ts +40 -4
- package/templates/node-worker/files/src/instrumentation.ts +110 -0
- package/templates/node-worker/files/src/logger.ts +14 -1
- package/templates/node-worker/files/src/telemetry/config.ts +61 -0
- package/templates/node-worker/files/src/temporal/activities/index.ts +14 -0
- package/templates/node-worker/files/src/temporal/worker.ts +84 -0
- package/templates/node-worker/files/src/temporal/workflows/index.ts +23 -0
- package/templates/node-worker/files/src/utils/worker.ts +10 -2
- package/templates/node-worker/template.json +43 -1
- package/templates/py-worker/files/.env.example +28 -0
- package/templates/py-worker/files/config.py +32 -1
- package/templates/py-worker/files/requirements.txt +16 -0
- package/templates/py-worker/files/temporal/__init__.py +0 -0
- package/templates/py-worker/files/temporal/activities.py +13 -0
- package/templates/py-worker/files/temporal/workflows.py +38 -0
- package/templates/py-worker/files/utils/db.py +20 -27
- package/templates/py-worker/files/utils/logger.py +9 -2
- package/templates/py-worker/files/utils/telemetry.py +154 -0
- package/templates/py-worker/files/utils/temporal.py +55 -0
- package/templates/py-worker/files/utils/worker.py +14 -3
- package/templates/py-worker/files/worker.py +16 -0
- package/templates/py-worker/template.json +36 -0
- package/templates/react-app/derive.sh +2 -1
- package/templates/react-app/files/.bin/secrets.js +8 -2
- package/templates/react-app/files/.env.example +11 -0
- package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +3 -0
- package/templates/react-app/files/Dockerfile +10 -0
- package/templates/react-app/files/_gitignore +1 -0
- package/templates/react-app/files/build.sh +3 -1
- package/templates/react-app/files/package.json +2 -0
- package/templates/react-app/files/src/lib/env.ts +10 -0
- package/templates/react-app/files/src/main.tsx +4 -0
- package/templates/react-app/files/src/shared/auth/provider.tsx +2 -0
- package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +2 -0
- package/templates/react-app/files/src/shared/telemetry/index.ts +80 -0
- package/templates/react-app/template.json +16 -2
- package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +15 -11
- package/templates/react-monorepo/files/Dockerfile +21 -7
- package/templates/react-monorepo/files/_gitignore +1 -0
- package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/admin/.env.example +11 -0
- package/templates/react-monorepo/files/apps/admin/package.json +1 -0
- package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +11 -7
- package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/admin/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/auth/.env.example +11 -0
- package/templates/react-monorepo/files/apps/auth/package.json +1 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/auth/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/client/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/client/.env.example +11 -0
- package/templates/react-monorepo/files/apps/client/package.json +1 -0
- package/templates/react-monorepo/files/apps/client/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/client/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +1 -1
- package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/landing/.env.example +11 -0
- package/templates/react-monorepo/files/apps/landing/package.json +1 -0
- package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/landing/src/main.tsx +4 -0
- package/templates/react-monorepo/files/build.sh +3 -1
- package/templates/react-monorepo/files/packages/auth/package.json +1 -0
- package/templates/react-monorepo/files/packages/auth/src/provider.tsx +2 -0
- package/templates/react-monorepo/files/packages/layouts/package.json +1 -0
- package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +2 -0
- package/templates/react-monorepo/files/packages/telemetry/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/telemetry/package.json +20 -0
- package/templates/react-monorepo/files/packages/telemetry/src/index.ts +80 -0
- package/templates/react-monorepo/files/packages/telemetry/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/types/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/turbo.json +1 -1
- package/templates/react-monorepo/template.json +87 -9
- package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +0 -21
- package/templates/nest-api/files/src/common/interceptors/index.ts +0 -1
- package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +0 -37
- package/templates/nest-api/files/src/common/modules/metrics.module.ts +0 -28
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initTelemetry } from '@acme/telemetry'; // @feature telemetry
|
|
4
|
+
import { API_URL, TELEMETRY } from './lib/env'; // @feature telemetry
|
|
3
5
|
import { RouterProvider } from 'react-router';
|
|
4
6
|
import '@acme/ui/globals.css';
|
|
5
7
|
import { Providers } from './app/providers';
|
|
6
8
|
import { router } from './app/router';
|
|
7
9
|
|
|
10
|
+
initTelemetry({ service: 'acme-landing', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
|
|
11
|
+
|
|
8
12
|
createRoot(document.getElementById('root')!).render(
|
|
9
13
|
<StrictMode>
|
|
10
14
|
<Providers>
|
|
@@ -46,7 +46,9 @@ read_var() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
BUILD_ARGS="--build-arg APP=$APP"
|
|
49
|
-
|
|
49
|
+
VARS="VITE_APP_API_URL VITE_APP_AUTH_HOST VITE_APP_CLIENT_HOST VITE_APP_ADMIN_HOST VITE_APP_LANDING VITE_APP_SUPPORT_EMAIL VITE_APP_SITE_URL"
|
|
50
|
+
VARS="$VARS VITE_OPENOBSERVE_URL VITE_OPENOBSERVE_ORG VITE_OPENOBSERVE_CLIENT_TOKEN" # @feature telemetry
|
|
51
|
+
for VAR in $VARS; do
|
|
50
52
|
VALUE="$(read_var "$VAR")"
|
|
51
53
|
[ -n "$VALUE" ] && BUILD_ARGS="$BUILD_ARGS --build-arg $VAR=$VALUE"
|
|
52
54
|
done
|
|
@@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux';
|
|
|
3
3
|
import { getAuthClient } from './client';
|
|
4
4
|
import { sessionCleared, sessionLoaded, sessionLoading, type AuthUser } from './slice';
|
|
5
5
|
import { AUTH_BROADCAST_CHANNEL } from './logout';
|
|
6
|
+
import { setTelemetryUser } from '@acme/telemetry'; // @feature telemetry
|
|
6
7
|
|
|
7
8
|
function toAuthUser(user: Record<string, unknown> | null | undefined): AuthUser | null {
|
|
8
9
|
if (!user) return null;
|
|
@@ -33,6 +34,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
33
34
|
}
|
|
34
35
|
const user = (session.data?.user as Record<string, unknown> | undefined) ?? null;
|
|
35
36
|
dispatch(sessionLoaded(toAuthUser(user)));
|
|
37
|
+
setTelemetryUser(user ? { id: String(user.id) } : null); // @feature telemetry
|
|
36
38
|
}, [session.isPending, session.data, dispatch]);
|
|
37
39
|
|
|
38
40
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
2
|
import { Button } from '@acme/ui';
|
|
3
3
|
import { StateScreen } from './states/state-screen';
|
|
4
|
+
import { reportError } from '@acme/telemetry'; // @feature telemetry
|
|
4
5
|
|
|
5
6
|
type ErrorBoundaryProps = {
|
|
6
7
|
children: ReactNode;
|
|
@@ -21,6 +22,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
21
22
|
|
|
22
23
|
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
23
24
|
console.error('ErrorBoundary caught an error', error, info);
|
|
25
|
+
reportError(error, { componentStack: info.componentStack }); // @feature telemetry
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
render(): ReactNode {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@acme/telemetry",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@openobserve/browser-logs": "^0.4.3",
|
|
15
|
+
"@openobserve/browser-rum": "^0.4.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@acme/config": "workspace:*"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @acme/telemetry — browser monitoring (RUM) and logs, sent to OpenObserve.
|
|
2
|
+
//
|
|
3
|
+
// Page views, errors, slow resources and user actions, plus console errors as
|
|
4
|
+
// logs. Requests to the app's own API carry a W3C `traceparent` header, so a
|
|
5
|
+
// click links to the backend trace it caused. The SDK finishes starting a
|
|
6
|
+
// moment after init (its session store is async), so the first request or two
|
|
7
|
+
// of a page load (typically the session check) go out without the header.
|
|
8
|
+
// Off until connection settings are passed; everything here is then a no-op.
|
|
9
|
+
import { DefaultPrivacyLevel, openobserveRum } from '@openobserve/browser-rum';
|
|
10
|
+
import { openobserveLogs } from '@openobserve/browser-logs';
|
|
11
|
+
|
|
12
|
+
export interface TelemetryOptions {
|
|
13
|
+
/** Service name, e.g. `acme-client` — one per app. */
|
|
14
|
+
service: string;
|
|
15
|
+
/** OpenObserve base URL, e.g. `https://o2.example.com`. */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** The project's organization identifier. */
|
|
18
|
+
org?: string;
|
|
19
|
+
/**
|
|
20
|
+
* A RUM client token (Ingestion → RUM). Write-only and built for shipping
|
|
21
|
+
* in a bundle — never the backend's ingestion token.
|
|
22
|
+
*/
|
|
23
|
+
clientToken?: string;
|
|
24
|
+
/** `development`, `production`, … — Vite's `import.meta.env.MODE`. */
|
|
25
|
+
environment?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
/** Backend origin(s) whose requests should carry trace headers. */
|
|
28
|
+
apiUrls?: string[];
|
|
29
|
+
/** Share of sessions to record for replay, 0–100. Off by default. */
|
|
30
|
+
sessionReplaySampleRate?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let started = false;
|
|
34
|
+
|
|
35
|
+
export function initTelemetry(options: TelemetryOptions): boolean {
|
|
36
|
+
const { url, org, clientToken } = options;
|
|
37
|
+
if (started || !url || !org || !clientToken) return false;
|
|
38
|
+
|
|
39
|
+
// The SDK takes a bare host (no scheme) plus an explicit plain-HTTP flag.
|
|
40
|
+
const { host, protocol } = new URL(url);
|
|
41
|
+
const common = {
|
|
42
|
+
clientToken,
|
|
43
|
+
site: host,
|
|
44
|
+
organizationIdentifier: org,
|
|
45
|
+
insecureHTTP: protocol === 'http:',
|
|
46
|
+
service: options.service,
|
|
47
|
+
env: options.environment,
|
|
48
|
+
version: options.version
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
openobserveRum.init({
|
|
52
|
+
...common,
|
|
53
|
+
applicationId: options.service,
|
|
54
|
+
sessionSampleRate: 100,
|
|
55
|
+
sessionReplaySampleRate: options.sessionReplaySampleRate ?? 0,
|
|
56
|
+
defaultPrivacyLevel: DefaultPrivacyLevel.MASK_USER_INPUT,
|
|
57
|
+
trackResources: true,
|
|
58
|
+
trackLongTasks: true,
|
|
59
|
+
trackUserInteractions: true,
|
|
60
|
+
allowedTracingUrls: (options.apiUrls ?? []).filter(Boolean).map((match) => ({
|
|
61
|
+
match,
|
|
62
|
+
propagatorTypes: ['tracecontext' as const]
|
|
63
|
+
}))
|
|
64
|
+
});
|
|
65
|
+
openobserveLogs.init({ ...common, forwardErrorsToLogs: true });
|
|
66
|
+
started = true;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Report a handled error — e.g. from an error boundary. */
|
|
71
|
+
export function reportError(error: unknown, context?: object): void {
|
|
72
|
+
if (started) openobserveRum.addError(error, context);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Tie the session to a signed-in user (id only), or clear it on logout. */
|
|
76
|
+
export function setTelemetryUser(user: { id: string } | null): void {
|
|
77
|
+
if (!started) return;
|
|
78
|
+
if (user) openobserveRum.setUser({ id: user.id });
|
|
79
|
+
else openobserveRum.clearUser();
|
|
80
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -52,6 +52,11 @@ const env = normalizeEnv(parseEnvArg());
|
|
|
52
52
|
const envFile = ENV_FILES[env];
|
|
53
53
|
|
|
54
54
|
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Export first and write only on success: a shell `>` redirect would empty
|
|
56
|
+
// the existing file before infisical had the chance to fail. execSync throws
|
|
57
|
+
// (and the process exits non-zero) when infisical fails.
|
|
58
|
+
const output = execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env}`, {
|
|
59
|
+
stdio: ['inherit', 'pipe', 'inherit']
|
|
57
60
|
});
|
|
61
|
+
writeFileSync(envFile, output);
|
|
62
|
+
console.log(`Wrote ${envFile}`);
|
|
@@ -81,6 +81,52 @@
|
|
|
81
81
|
"packages/types/**"
|
|
82
82
|
]
|
|
83
83
|
},
|
|
84
|
+
"telemetry": {
|
|
85
|
+
"title": "Browser monitoring → OpenObserve",
|
|
86
|
+
"description": "The @acme/telemetry package: OpenObserve RUM and logs in every app — page views, errors (including the error boundary's), slow resources, user actions, the signed-in user id, and traceparent headers on API calls so frontend and backend traces link up. Session replay is off unless a project turns it on. Off at runtime until VITE_OPENOBSERVE_* are set.",
|
|
87
|
+
"default": true,
|
|
88
|
+
"files": [
|
|
89
|
+
"packages/telemetry/**"
|
|
90
|
+
],
|
|
91
|
+
"packageJson": [
|
|
92
|
+
{
|
|
93
|
+
"file": "apps/admin/package.json",
|
|
94
|
+
"dependencies": [
|
|
95
|
+
"@acme/telemetry"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"file": "apps/auth/package.json",
|
|
100
|
+
"dependencies": [
|
|
101
|
+
"@acme/telemetry"
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"file": "apps/client/package.json",
|
|
106
|
+
"dependencies": [
|
|
107
|
+
"@acme/telemetry"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"file": "apps/landing/package.json",
|
|
112
|
+
"dependencies": [
|
|
113
|
+
"@acme/telemetry"
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"file": "packages/layouts/package.json",
|
|
118
|
+
"dependencies": [
|
|
119
|
+
"@acme/telemetry"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"file": "packages/auth/package.json",
|
|
124
|
+
"dependencies": [
|
|
125
|
+
"@acme/telemetry"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
},
|
|
84
130
|
"app-auth": {
|
|
85
131
|
"title": "Auth app",
|
|
86
132
|
"description": "Login, signup, forgot/reset password, email verification. The one app that owns the session; the others redirect here.",
|
|
@@ -99,14 +145,14 @@
|
|
|
99
145
|
},
|
|
100
146
|
"app-admin": {
|
|
101
147
|
"title": "Admin console",
|
|
102
|
-
"description": "
|
|
148
|
+
"description": "Internal console over the same data layer, gated to the admin and superuser roles in the UI. The API must enforce the role on every endpoint it calls.",
|
|
103
149
|
"files": [
|
|
104
150
|
"apps/admin/**"
|
|
105
151
|
]
|
|
106
152
|
},
|
|
107
153
|
"app-landing": {
|
|
108
154
|
"title": "Marketing site",
|
|
109
|
-
"description": "Public
|
|
155
|
+
"description": "Public marketing site: a home page, an SEO route table, and build-time robots.txt, sitemap.xml and security.txt.",
|
|
110
156
|
"files": [
|
|
111
157
|
"apps/landing/**"
|
|
112
158
|
]
|
|
@@ -127,7 +173,7 @@
|
|
|
127
173
|
},
|
|
128
174
|
"pwa": {
|
|
129
175
|
"title": "Installable PWA (client app)",
|
|
130
|
-
"description": "vite-plugin-pwa: app-shell offline caching,
|
|
176
|
+
"description": "vite-plugin-pwa configured for the client app: app-shell offline caching, and registerType 'prompt' so a new build waits for the user instead of swapping assets under a running app. The update prompt and install button are yours to build with the plugin's useRegisterSW.",
|
|
131
177
|
"requires": [
|
|
132
178
|
"app-client"
|
|
133
179
|
],
|
|
@@ -140,7 +186,7 @@
|
|
|
140
186
|
},
|
|
141
187
|
"charts": {
|
|
142
188
|
"title": "Charts (client app)",
|
|
143
|
-
"description": "ApexCharts
|
|
189
|
+
"description": "ApexCharts and react-apexcharts in the client app, with a theme-aware chart palette (the --chart-* tokens in the UI kit). No chart components ship.",
|
|
144
190
|
"requires": [
|
|
145
191
|
"app-client"
|
|
146
192
|
],
|
|
@@ -193,11 +239,43 @@
|
|
|
193
239
|
"apps/*/.bin/**",
|
|
194
240
|
"packages/types/.bin/**"
|
|
195
241
|
],
|
|
196
|
-
"packageJson":
|
|
197
|
-
|
|
198
|
-
"
|
|
199
|
-
|
|
200
|
-
|
|
242
|
+
"packageJson": [
|
|
243
|
+
{
|
|
244
|
+
"scripts": [
|
|
245
|
+
"secrets"
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"file": "apps/admin/package.json",
|
|
250
|
+
"scripts": [
|
|
251
|
+
"secrets"
|
|
252
|
+
]
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"file": "apps/auth/package.json",
|
|
256
|
+
"scripts": [
|
|
257
|
+
"secrets"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"file": "apps/client/package.json",
|
|
262
|
+
"scripts": [
|
|
263
|
+
"secrets"
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"file": "apps/landing/package.json",
|
|
268
|
+
"scripts": [
|
|
269
|
+
"secrets"
|
|
270
|
+
]
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"file": "packages/types/package.json",
|
|
274
|
+
"scripts": [
|
|
275
|
+
"secrets"
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
]
|
|
201
279
|
}
|
|
202
280
|
},
|
|
203
281
|
"hooks": [
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Controller, Get, Res } from '@nestjs/common';
|
|
2
|
-
import { ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
|
|
3
|
-
import { SkipThrottle } from '@nestjs/throttler'; // @feature rate-limit
|
|
4
|
-
import { AllowAnonymous } from '@thallesp/nestjs-better-auth';
|
|
5
|
-
import { PrometheusController } from '@willsoto/nestjs-prometheus';
|
|
6
|
-
import type { Response } from 'express';
|
|
7
|
-
|
|
8
|
-
@ApiTags('App')
|
|
9
|
-
@Controller()
|
|
10
|
-
// @feature:start rate-limit
|
|
11
|
-
// Scraped on a fixed interval by Prometheus; already gated by basic auth.
|
|
12
|
-
@SkipThrottle()
|
|
13
|
-
// @feature:end
|
|
14
|
-
export class MetricsController extends PrometheusController {
|
|
15
|
-
@Get()
|
|
16
|
-
@ApiExcludeEndpoint()
|
|
17
|
-
@AllowAnonymous()
|
|
18
|
-
async index(@Res({ passthrough: true }) response: Response): Promise<string> {
|
|
19
|
-
return super.index(response);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './metrics.interceptor'; // @feature observability
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Injectable,
|
|
3
|
-
NestInterceptor,
|
|
4
|
-
ExecutionContext,
|
|
5
|
-
CallHandler
|
|
6
|
-
} from '@nestjs/common';
|
|
7
|
-
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
|
8
|
-
import { Histogram } from 'prom-client';
|
|
9
|
-
import { Observable, tap } from 'rxjs';
|
|
10
|
-
|
|
11
|
-
@Injectable()
|
|
12
|
-
export class MetricsInterceptor implements NestInterceptor {
|
|
13
|
-
constructor(
|
|
14
|
-
@InjectMetric('http_request_duration_seconds')
|
|
15
|
-
private readonly histogram: Histogram<string>
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
19
|
-
if (context.getType() !== 'http') {
|
|
20
|
-
return next.handle();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const http = context.switchToHttp();
|
|
24
|
-
const req = http.getRequest();
|
|
25
|
-
const res = http.getResponse();
|
|
26
|
-
const stop = this.histogram.startTimer();
|
|
27
|
-
|
|
28
|
-
const observe = () =>
|
|
29
|
-
stop({
|
|
30
|
-
method: req.method,
|
|
31
|
-
route: req.route?.path ?? 'unmatched',
|
|
32
|
-
status: res.statusCode
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return next.handle().pipe(tap({ next: observe, error: observe }));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Module } from '@nestjs/common';
|
|
2
|
-
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
|
-
import {
|
|
4
|
-
makeHistogramProvider,
|
|
5
|
-
PrometheusModule
|
|
6
|
-
} from '@willsoto/nestjs-prometheus';
|
|
7
|
-
import { MetricsController } from '../controllers';
|
|
8
|
-
import { MetricsInterceptor } from '../interceptors';
|
|
9
|
-
|
|
10
|
-
@Module({
|
|
11
|
-
imports: [
|
|
12
|
-
PrometheusModule.register({
|
|
13
|
-
defaultLabels: { app: 'acme-api' },
|
|
14
|
-
defaultMetrics: { enabled: true },
|
|
15
|
-
controller: MetricsController
|
|
16
|
-
})
|
|
17
|
-
],
|
|
18
|
-
providers: [
|
|
19
|
-
makeHistogramProvider({
|
|
20
|
-
name: 'http_request_duration_seconds',
|
|
21
|
-
help: 'Duration of HTTP requests in seconds',
|
|
22
|
-
labelNames: ['method', 'route', 'status'],
|
|
23
|
-
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
|
|
24
|
-
}),
|
|
25
|
-
{ provide: APP_INTERCEPTOR, useClass: MetricsInterceptor }
|
|
26
|
-
]
|
|
27
|
-
})
|
|
28
|
-
export class MetricsModule {}
|