@aurostack/stacks 0.1.0 → 0.2.0

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.
Files changed (88) hide show
  1. package/README.md +84 -0
  2. package/cli/src/generate.mjs +13 -1
  3. package/cli/src/hooks.mjs +10 -3
  4. package/cli/stack.mjs +3 -0
  5. package/package.json +4 -1
  6. package/templates/nest-api/files/.env.example +29 -2
  7. package/templates/nest-api/files/.env.test.example +1 -1
  8. package/templates/nest-api/files/compose.yml +16 -0
  9. package/templates/nest-api/files/package.json +19 -4
  10. package/templates/nest-api/files/src/app.module.ts +9 -1
  11. package/templates/nest-api/files/src/app.setup.ts +5 -6
  12. package/templates/nest-api/files/src/common/__tests__/temporal.service.spec.ts +66 -0
  13. package/templates/nest-api/files/src/common/common.module.ts +4 -3
  14. package/templates/nest-api/files/src/common/controllers/index.ts +0 -1
  15. package/templates/nest-api/files/src/common/modules/index.ts +0 -1
  16. package/templates/nest-api/files/src/common/modules/logger.module.ts +2 -6
  17. package/templates/nest-api/files/src/common/services/config.service.ts +43 -5
  18. package/templates/nest-api/files/src/common/services/index.ts +1 -0
  19. package/templates/nest-api/files/src/common/services/temporal.service.ts +66 -0
  20. package/templates/nest-api/files/src/instrumentation.ts +117 -0
  21. package/templates/nest-api/files/src/main.ts +3 -1
  22. package/templates/nest-api/files/src/telemetry/__tests__/config.spec.ts +67 -0
  23. package/templates/nest-api/files/src/telemetry/config.ts +62 -0
  24. package/templates/nest-api/template.json +37 -11
  25. package/templates/node-worker/files/.env.example +27 -0
  26. package/templates/node-worker/files/.prettierignore +2 -0
  27. package/templates/node-worker/files/Dockerfile +25 -2
  28. package/templates/node-worker/files/Dockerfile.dev +25 -2
  29. package/templates/node-worker/files/ecosystem.config.cjs +2 -0
  30. package/templates/node-worker/files/package.json +23 -4
  31. package/templates/node-worker/files/src/config.ts +23 -0
  32. package/templates/node-worker/files/src/index.ts +13 -0
  33. package/templates/node-worker/files/src/instrumentation.ts +110 -0
  34. package/templates/node-worker/files/src/telemetry/config.ts +61 -0
  35. package/templates/node-worker/files/src/temporal/activities/index.ts +14 -0
  36. package/templates/node-worker/files/src/temporal/worker.ts +84 -0
  37. package/templates/node-worker/files/src/temporal/workflows/index.ts +23 -0
  38. package/templates/node-worker/files/src/utils/worker.ts +9 -1
  39. package/templates/node-worker/template.json +44 -2
  40. package/templates/py-worker/files/.env.example +28 -0
  41. package/templates/py-worker/files/config.py +32 -1
  42. package/templates/py-worker/files/requirements.txt +16 -0
  43. package/templates/py-worker/files/temporal/__init__.py +0 -0
  44. package/templates/py-worker/files/temporal/activities.py +13 -0
  45. package/templates/py-worker/files/temporal/workflows.py +38 -0
  46. package/templates/py-worker/files/utils/telemetry.py +154 -0
  47. package/templates/py-worker/files/utils/temporal.py +55 -0
  48. package/templates/py-worker/files/utils/worker.py +12 -1
  49. package/templates/py-worker/files/worker.py +16 -0
  50. package/templates/py-worker/template.json +36 -0
  51. package/templates/react-app/derive.sh +2 -1
  52. package/templates/react-app/files/.env.example +11 -0
  53. package/templates/react-app/files/package.json +2 -0
  54. package/templates/react-app/files/src/lib/env.ts +10 -0
  55. package/templates/react-app/files/src/main.tsx +4 -0
  56. package/templates/react-app/files/src/shared/auth/provider.tsx +2 -0
  57. package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +2 -0
  58. package/templates/react-app/files/src/shared/telemetry/index.ts +80 -0
  59. package/templates/react-app/template.json +15 -1
  60. package/templates/react-monorepo/files/apps/admin/.env.example +11 -0
  61. package/templates/react-monorepo/files/apps/admin/package.json +1 -0
  62. package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +10 -0
  63. package/templates/react-monorepo/files/apps/admin/src/main.tsx +4 -0
  64. package/templates/react-monorepo/files/apps/auth/.env.example +11 -0
  65. package/templates/react-monorepo/files/apps/auth/package.json +1 -0
  66. package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +10 -0
  67. package/templates/react-monorepo/files/apps/auth/src/main.tsx +4 -0
  68. package/templates/react-monorepo/files/apps/client/.env.example +11 -0
  69. package/templates/react-monorepo/files/apps/client/package.json +1 -0
  70. package/templates/react-monorepo/files/apps/client/src/lib/env.ts +10 -0
  71. package/templates/react-monorepo/files/apps/client/src/main.tsx +4 -0
  72. package/templates/react-monorepo/files/apps/landing/.env.example +11 -0
  73. package/templates/react-monorepo/files/apps/landing/package.json +1 -0
  74. package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
  75. package/templates/react-monorepo/files/apps/landing/src/main.tsx +4 -0
  76. package/templates/react-monorepo/files/packages/auth/package.json +1 -0
  77. package/templates/react-monorepo/files/packages/auth/src/provider.tsx +2 -0
  78. package/templates/react-monorepo/files/packages/layouts/package.json +1 -0
  79. package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +2 -0
  80. package/templates/react-monorepo/files/packages/telemetry/eslint.config.mjs +3 -0
  81. package/templates/react-monorepo/files/packages/telemetry/package.json +20 -0
  82. package/templates/react-monorepo/files/packages/telemetry/src/index.ts +80 -0
  83. package/templates/react-monorepo/files/packages/telemetry/tsconfig.json +4 -0
  84. package/templates/react-monorepo/template.json +47 -1
  85. package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +0 -21
  86. package/templates/nest-api/files/src/common/interceptors/index.ts +0 -1
  87. package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +0 -37
  88. package/templates/nest-api/files/src/common/modules/metrics.module.ts +0 -28
@@ -14,3 +14,14 @@ VITE_APP_ADMIN_HOST='http://localhost:3003'
14
14
 
15
15
  # This app's own dev port.
16
16
  PORT='3002'
17
+
18
+ # @feature:start telemetry
19
+ # OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
20
+ # trace headers on API calls. Leave empty and it stays off. The client token is
21
+ # a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
22
+ # the bundle — never the backend's ingestion token.
23
+ # The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
24
+ VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
25
+ VITE_OPENOBSERVE_ORG=''
26
+ VITE_OPENOBSERVE_CLIENT_TOKEN=''
27
+ # @feature:end
@@ -15,6 +15,7 @@
15
15
  "@acme/api": "workspace:*",
16
16
  "@acme/auth": "workspace:*",
17
17
  "@acme/layouts": "workspace:*",
18
+ "@acme/telemetry": "workspace:*",
18
19
  "@acme/ui": "workspace:*",
19
20
  "@reduxjs/toolkit": "^2.12.0",
20
21
  "apexcharts": "^6.5.0",
@@ -10,3 +10,13 @@ export const CLIENT_URL = env.VITE_APP_CLIENT_HOST as string;
10
10
  export const ADMIN_URL = env.VITE_APP_ADMIN_HOST as string;
11
11
  /** The public marketing site (logout destination). */
12
12
  export const LANDING_URL = env.VITE_APP_LANDING as string;
13
+
14
+ // @feature:start telemetry
15
+ /** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
16
+ export const TELEMETRY = {
17
+ url: env.VITE_OPENOBSERVE_URL as string | undefined,
18
+ org: env.VITE_OPENOBSERVE_ORG as string | undefined,
19
+ clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
20
+ environment: env.MODE
21
+ };
22
+ // @feature:end
@@ -1,5 +1,7 @@
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';
@@ -10,6 +12,8 @@ import { router } from './app/router';
10
12
  // evaluating and `providers` has already called `configureApi` at module scope,
11
13
  // so the fetch races the session check instead of queueing behind it.
12
14
 
15
+ initTelemetry({ service: 'acme-client', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
16
+
13
17
  createRoot(document.getElementById('root')!).render(
14
18
  <StrictMode>
15
19
  <Providers>
@@ -18,3 +18,14 @@ PORT='3000'
18
18
  # Canonical public URL, used for sitemap/robots/OG tags at build time.
19
19
  VITE_APP_SITE_URL='http://localhost:3000'
20
20
  VITE_APP_SUPPORT_EMAIL='support@example.com'
21
+
22
+ # @feature:start telemetry
23
+ # OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
24
+ # trace headers on API calls. Leave empty and it stays off. The client token is
25
+ # a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
26
+ # the bundle — never the backend's ingestion token.
27
+ # The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
28
+ VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
29
+ VITE_OPENOBSERVE_ORG=''
30
+ VITE_OPENOBSERVE_CLIENT_TOKEN=''
31
+ # @feature:end
@@ -15,6 +15,7 @@
15
15
  "@acme/api": "workspace:*",
16
16
  "@acme/auth": "workspace:*",
17
17
  "@acme/layouts": "workspace:*",
18
+ "@acme/telemetry": "workspace:*",
18
19
  "@acme/types": "workspace:*",
19
20
  "@acme/ui": "workspace:*",
20
21
  "@reduxjs/toolkit": "^2.12.0",
@@ -8,3 +8,13 @@ export const CLIENT_HOST = env.VITE_APP_CLIENT_HOST;
8
8
  * actually posts. Falsy when the build arg is missing; the UI hides the block
9
9
  * rather than rendering `mailto:undefined`. */
10
10
  export const SUPPORT_EMAIL = env.VITE_APP_SUPPORT_EMAIL;
11
+
12
+ // @feature:start telemetry
13
+ /** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
14
+ export const TELEMETRY = {
15
+ url: env.VITE_OPENOBSERVE_URL as string | undefined,
16
+ org: env.VITE_OPENOBSERVE_ORG as string | undefined,
17
+ clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
18
+ environment: env.MODE
19
+ };
20
+ // @feature:end
@@ -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>
@@ -11,6 +11,7 @@
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
+ "@acme/telemetry": "workspace:*",
14
15
  "better-auth": "^1.6.2"
15
16
  },
16
17
  "peerDependencies": {
@@ -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(() => {
@@ -11,6 +11,7 @@
11
11
  "typecheck": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
+ "@acme/telemetry": "workspace:*",
14
15
  "@acme/ui": "workspace:*",
15
16
  "lucide-react": "^1.17.0"
16
17
  },
@@ -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,3 @@
1
+ import config from '@acme/config/eslint';
2
+
3
+ export default config;
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "@acme/config/tsconfig/react.json",
3
+ "include": ["src"]
4
+ }
@@ -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.",
@@ -208,7 +254,7 @@
208
254
  },
209
255
  {
210
256
  "title": "yarn install",
211
- "run": "yarn install",
257
+ "run": "yarn install --no-immutable",
212
258
  "optional": true
213
259
  },
214
260
  {
@@ -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 {}