From 3f3413a230e73266ac3cb3fb6917d73b3171547a Mon Sep 17 00:00:00 2001 From: Mridang Agarwalla Date: Wed, 4 Mar 2026 21:28:56 +1100 Subject: [PATCH] feat(login): added support for otel push-based logs, metrics, and traces (#11429) --- apps/login/.eslintrc.cjs | 51 +- apps/login/.gitignore | 1 + apps/login/Dockerfile | 6 +- .../dockerized/ca/ca.integration.test.ts | 205 +- apps/login/dockerized/ca/mock-server.ts | 38 - .../dockerized/otel/mock-zitadel-server.js | 155 ++ .../otel/otel-collector-config.yaml | 43 + .../dockerized/otel/otel.integration.test.ts | 1205 ++++++++++++ .../dockerized/ssl/tls.integration.test.ts | 4 +- apps/login/next.config.mjs | 22 +- apps/login/package.json | 18 +- apps/login/project.json | 14 +- apps/login/scripts/entrypoint.sh | 5 + apps/login/scripts/server.mjs | 3 +- apps/login/src/app/otel-test/route.ts | 85 + apps/login/src/instrumentation.ts | 87 + apps/login/src/lib/cookies.test.ts | 29 +- apps/login/src/lib/cookies.ts | 7 +- apps/login/src/lib/fingerprint.test.ts | 8 +- .../src/lib/grpc/interceptors/otel.test.ts | 209 +++ apps/login/src/lib/grpc/interceptors/otel.ts | 98 + apps/login/src/lib/grpc/interceptors/types.ts | 18 + apps/login/src/lib/logger.ts | 71 + apps/login/src/lib/metrics.ts | 146 ++ apps/login/src/lib/server/auth-flow.ts | 7 +- apps/login/src/lib/server/cookie.ts | 9 +- apps/login/src/lib/server/flow-initiation.ts | 13 +- apps/login/src/lib/server/idp-intent.ts | 81 +- apps/login/src/lib/server/loginname.ts | 37 +- apps/login/src/lib/server/passkeys.ts | 7 +- apps/login/src/lib/server/password.ts | 36 +- apps/login/src/lib/server/session.ts | 7 +- apps/login/src/lib/server/verify.test.ts | 10 +- apps/login/src/lib/server/verify.ts | 9 +- apps/login/src/lib/verify-helper.test.ts | 18 +- apps/login/src/lib/zitadel.ts | 82 +- apps/login/src/middleware.ts | 9 +- apps/login/test-mocks/server-only.ts | 4 + apps/login/vitest.config.ca.mts | 11 - apps/login/vitest.config.mts | 11 +- pnpm-lock.yaml | 1653 ++++++++++++++++- 41 files changed, 4182 insertions(+), 350 deletions(-) delete mode 100644 apps/login/dockerized/ca/mock-server.ts create mode 100644 apps/login/dockerized/otel/mock-zitadel-server.js create mode 100644 apps/login/dockerized/otel/otel-collector-config.yaml create mode 100644 apps/login/dockerized/otel/otel.integration.test.ts create mode 100644 apps/login/src/app/otel-test/route.ts create mode 100644 apps/login/src/instrumentation.ts create mode 100644 apps/login/src/lib/grpc/interceptors/otel.test.ts create mode 100644 apps/login/src/lib/grpc/interceptors/otel.ts create mode 100644 apps/login/src/lib/grpc/interceptors/types.ts create mode 100644 apps/login/src/lib/logger.ts create mode 100644 apps/login/src/lib/metrics.ts create mode 100644 apps/login/test-mocks/server-only.ts delete mode 100644 apps/login/vitest.config.ca.mts diff --git a/apps/login/.eslintrc.cjs b/apps/login/.eslintrc.cjs index bbca76e6c..b94843c3e 100644 --- a/apps/login/.eslintrc.cjs +++ b/apps/login/.eslintrc.cjs @@ -1,8 +1,9 @@ module.exports = { parser: "@typescript-eslint/parser", extends: ["next", "prettier"], - plugins: ["@typescript-eslint"], + plugins: ["@typescript-eslint", "import"], rules: { + "import/first": "error", "@next/next/no-html-link-for-pages": "off", "@next/next/no-img-element": "off", "react/no-unescaped-entities": "off", @@ -27,4 +28,52 @@ module.exports = { }, project: "./tsconfig.json", }, + overrides: [ + { + // Server components and server actions: must use logger, not console.* + // Files in src/lib/server/*, src/app/**/route.ts, src/middleware.ts, and files with "use server" + files: [ + "src/lib/server/**/*.ts", + "src/app/**/route.ts", + "src/middleware.ts", + "src/lib/cookies.ts", + ], + rules: { + "no-console": "warn", + }, + }, + { + // instrumentation.ts uses console.log during OTEL initialization (before logger is available) + files: ["src/instrumentation.ts"], + rules: { + "no-console": "off", + }, + }, + { + // Client components: logger import is forbidden (enforced by server-only at build time) + // This override documents the expectation for client components + files: ["src/components/**/*.tsx"], + rules: { + "no-restricted-imports": ["error", { + "paths": [ + { + "name": "next/image", + "message": "Use of next/image is forbidden. Use regular elements instead." + }, + { + "name": "@/lib/logger", + "message": "Cannot import logger in client components. Use console.* instead. The logger uses 'server-only' and will fail at build time." + } + ] + }], + }, + }, + { + // Test files: allow console.* + files: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/tests/**/*"], + rules: { + "no-console": "off", + }, + }, + ], }; diff --git a/apps/login/.gitignore b/apps/login/.gitignore index b856d6e13..41a167438 100644 --- a/apps/login/.gitignore +++ b/apps/login/.gitignore @@ -13,4 +13,5 @@ dist-ssr *.local .vscode /blob-report/ +# Dockerized test output (generated by integration tests) dockerized/*/output/ diff --git a/apps/login/Dockerfile b/apps/login/Dockerfile index a76fabee0..4d34cf80c 100644 --- a/apps/login/Dockerfile +++ b/apps/login/Dockerfile @@ -14,9 +14,11 @@ ENV HOSTNAME="::" \ NODE_OPTIONS="--use-openssl-ca" \ SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt" \ SSL_CERT_DIR="/etc/ssl/certs" \ - ZITADEL_TLS_ENABLED="false" + ZITADEL_TLS_ENABLED="false" \ + OTEL_SERVICE_NAME="zitadel-login" \ + OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" # TODO: Check healthy, not ready HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD ["/usr/local/bin/node", "/app/healthcheck.mjs"] -ENTRYPOINT ["/app/entrypoint.sh", "node", "apps/login/server.js" ] +ENTRYPOINT ["/app/entrypoint.sh", "node", "apps/login/server.js"] diff --git a/apps/login/dockerized/ca/ca.integration.test.ts b/apps/login/dockerized/ca/ca.integration.test.ts index 143388250..fa3f0f733 100644 --- a/apps/login/dockerized/ca/ca.integration.test.ts +++ b/apps/login/dockerized/ca/ca.integration.test.ts @@ -1,165 +1,117 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; -import { GenericContainer, type StartedTestContainer, Network, Wait } from "testcontainers"; +import { + GenericContainer, + type StartedTestContainer, + Network, + Wait, +} from "testcontainers"; import type { StartedNetwork } from "testcontainers"; import * as fs from "fs"; +import * as os from "os"; import * as path from "path"; -import * as esbuild from "esbuild"; import { generateCertificates } from "./utils/tls.ts"; const TEST_DIR = path.dirname(new URL(import.meta.url).pathname); -const OUTPUT_DIR = path.join(TEST_DIR, "output"); -const CERTS_DIR = path.join(OUTPUT_DIR, "certs"); const LOGIN_APP_DIR = path.join(TEST_DIR, "../.."); -async function bundleMockServer(): Promise { - const bundleDir = path.join(OUTPUT_DIR, "bundle"); - fs.mkdirSync(bundleDir, { recursive: true }); - - const outfile = path.join(bundleDir, "mock-server.js"); - await esbuild.build({ - entryPoints: [path.join(TEST_DIR, "mock-server.ts")], - bundle: true, - platform: "node", - target: "node22", - outfile, - format: "cjs", - }); - - return outfile; -} - -function readCapturedRequests(): Array<{ method: string; url: string; tlsConnected: boolean }> { - const filePath = path.join(OUTPUT_DIR, "requests.json"); - if (!fs.existsSync(filePath)) { - return []; - } - return JSON.parse(fs.readFileSync(filePath, "utf-8")); -} +const SMOCKER_MOCK_PORT = 443; +const SMOCKER_ADMIN_PORT = 8081; /** * Integration tests for custom CA certificate support in the login application. * - * These tests verify that the Dockerfile correctly configures Node.js to trust custom - * Certificate Authorities via the SSL_CERT_FILE environment variable and the - * NODE_OPTIONS=--use-openssl-ca flag. This is essential for deployments where the - * ZITADEL API is served behind a TLS-terminating proxy using internal or self-signed - * certificates. + * These tests verify that the Dockerfile correctly configures Node.js to trust + * custom Certificate Authorities via the SSL_CERT_FILE environment variable and + * the NODE_OPTIONS=--use-openssl-ca flag. * - * The test suite spins up a mock TLS server with a self-signed certificate inside a - * Docker network. The login application container connects to this mock server, and - * the tests verify that TLS connections succeed when the custom CA is mounted and - * fail when it is not. The mock server records all incoming requests to a shared - * volume, allowing the tests to verify that TLS handshakes completed successfully. - * - * The Docker network ensures container-to-container communication uses internal DNS - * resolution, avoiding port conflicts on the host machine and ensuring reliable - * execution in CI environments. + * We use Smocker as a TLS server and test CA loading by making curl requests + * from inside the container - this verifies TLS at the transport layer without + * needing gRPC/HTTP2 support. */ +const LOGIN_IMAGE_TAG = `zitadel-login-test:${process.env.GITHUB_SHA || "local"}`; + +async function testTlsConnection( + container: StartedTestContainer, + url: string, +): Promise<{ exitCode: number; output: string }> { + return container.exec([ + "node", + "-e", + `fetch('${url}').then(() => process.exit(0)).catch(() => process.exit(1))`, + ]); +} + describe("Custom CA Certificate Integration", () => { - let certs: ReturnType; let network: StartedNetwork; let mockServer: StartedTestContainer; - let mockServerBundle: string; - let loginImage: GenericContainer; + let certsDir: string; beforeAll(async () => { - if (fs.existsSync(OUTPUT_DIR)) { - fs.rmSync(OUTPUT_DIR, { recursive: true }); - } - fs.mkdirSync(CERTS_DIR, { recursive: true }); + certsDir = fs.mkdtempSync(path.join(os.tmpdir(), "ca-test-certs-")); - certs = generateCertificates(); - - fs.writeFileSync(path.join(CERTS_DIR, "ca.crt"), certs.ca.cert); - fs.writeFileSync(path.join(CERTS_DIR, "server.key"), certs.server.key); - fs.writeFileSync(path.join(CERTS_DIR, "server.crt"), certs.server.cert); - - mockServerBundle = await bundleMockServer(); + const certs = generateCertificates(); + fs.writeFileSync(path.join(certsDir, "ca.crt"), certs.ca.cert); + fs.writeFileSync(path.join(certsDir, "server.key"), certs.server.key); + fs.writeFileSync(path.join(certsDir, "server.crt"), certs.server.cert); network = await new Network().start(); - mockServer = await new GenericContainer("node:22-alpine") + mockServer = await new GenericContainer("ghcr.io/smocker-dev/smocker") .withNetwork(network) .withNetworkAliases("mock-zitadel") - .withCopyFilesToContainer([ - { source: mockServerBundle, target: "/app/server.js" }, - { source: path.join(CERTS_DIR, "server.key"), target: "/certs/server.key" }, - { source: path.join(CERTS_DIR, "server.crt"), target: "/certs/server.crt" }, - ]) - .withBindMounts([{ source: OUTPUT_DIR, target: "/output" }]) - .withCommand(["node", "/app/server.js"]) - .withExposedPorts(443) - .withWaitStrategy(Wait.forLogMessage("Mock TLS server listening")) + .withBindMounts([{ source: certsDir, target: "/certs", mode: "ro" }]) + .withEnvironment({ + SMOCKER_MOCK_SERVER_LISTEN_PORT: "443", + SMOCKER_CONFIG_LISTEN_PORT: "8081", + SMOCKER_TLS_ENABLE: "true", + SMOCKER_TLS_CERT_FILE: "/certs/server.crt", + SMOCKER_TLS_PRIVATE_KEY_FILE: "/certs/server.key", + }) + .withExposedPorts(SMOCKER_MOCK_PORT, SMOCKER_ADMIN_PORT) + .withStartupTimeout(120_000) + .withWaitStrategy(Wait.forLogMessage(/Starting mock server/)) .start(); - loginImage = await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(); - }); + try { + await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(LOGIN_IMAGE_TAG); + } catch (err) { + throw new Error(`Failed to build login Docker image: ${err instanceof Error ? err.message : err}`); + } + }, 180_000); afterAll(async () => { - await mockServer?.stop(); - await network?.stop(); + await mockServer?.stop().catch(() => {}); + await network?.stop().catch(() => {}); }); describe("when custom CA certificate is provided", () => { let container: StartedTestContainer; - let appUrl: string; beforeAll(async () => { - if (fs.existsSync(path.join(OUTPUT_DIR, "requests.json"))) { - fs.unlinkSync(path.join(OUTPUT_DIR, "requests.json")); - } - - container = await loginImage + container = await new GenericContainer(LOGIN_IMAGE_TAG) .withNetwork(network) .withExposedPorts(3000) .withEnvironment({ - ZITADEL_API_URL: "https://mock-zitadel", - ZITADEL_SERVICE_USER_TOKEN: "test-token", SSL_CERT_FILE: "/etc/ssl/certs/custom-ca.crt", }) .withCopyFilesToContainer([ { - source: path.join(CERTS_DIR, "ca.crt"), + source: path.join(certsDir, "ca.crt"), target: "/etc/ssl/certs/custom-ca.crt", }, ]) + .withStartupTimeout(180_000) .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) .start(); - - appUrl = `http://${container.getHost()}:${container.getMappedPort(3000)}`; - - await fetch(`${appUrl}/ui/v2/login/loginname`, { redirect: "manual" }); - - for (let i = 0; i < 30; i++) { - const requests = readCapturedRequests(); - if (requests.length > 0) break; - await new Promise((r) => setTimeout(r, 1000)); - } - }); + }, 180_000); afterAll(async () => { - await container?.stop(); + await container?.stop().catch(() => {}); }); - it("connects to mock server over TLS", () => { - const requests = readCapturedRequests(); - expect(requests.length).toBeGreaterThan(0); - }); - - it("marks requests as TLS connected", () => { - const requests = readCapturedRequests(); - const tlsRequests = requests.filter((r) => r.tlsConnected === true); - expect(tlsRequests.length).toBeGreaterThan(0); - }); - - it("returns 200 from healthy endpoint", async () => { - const response = await fetch(`${appUrl}/ui/v2/login/healthy`); - expect(response.status).toBe(200); - }); - - it("serves the login page", async () => { - const response = await fetch(`${appUrl}/ui/v2/login/loginname`, { redirect: "manual" }); - expect([200, 302, 303, 307, 308]).toContain(response.status); + it("can establish TLS connection to server with custom CA", async () => { + const result = await testTlsConnection(container, "https://mock-zitadel/"); + expect(result.exitCode).toBe(0); }); it("has NODE_OPTIONS set with --use-openssl-ca", async () => { @@ -184,33 +136,23 @@ describe("Custom CA Certificate Integration", () => { describe("when custom CA certificate is not provided", () => { let container: StartedTestContainer; - let appUrl: string; - let requestsBefore: number; beforeAll(async () => { - requestsBefore = readCapturedRequests().length; - - container = await loginImage + container = await new GenericContainer(LOGIN_IMAGE_TAG) .withNetwork(network) .withExposedPorts(3000) - .withEnvironment({ - ZITADEL_API_URL: "https://mock-zitadel", - ZITADEL_SERVICE_USER_TOKEN: "test-token", - }) + .withStartupTimeout(180_000) .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) .start(); - - appUrl = `http://${container.getHost()}:${container.getMappedPort(3000)}`; - - await fetch(`${appUrl}/ui/v2/login/loginname`, { redirect: "manual" }); - - for (let i = 0; i < 10; i++) { - await new Promise((r) => setTimeout(r, 500)); - } - }); + }, 180_000); afterAll(async () => { - await container?.stop(); + await container?.stop().catch(() => {}); + }); + + it("cannot establish TLS connection without custom CA", async () => { + const result = await testTlsConnection(container, "https://mock-zitadel/"); + expect(result.exitCode).not.toBe(0); }); it("uses system CA store by default", async () => { @@ -226,10 +168,5 @@ describe("Custom CA Certificate Integration", () => { ]); expect(result.output.trim()).toContain("missing"); }); - - it("cannot establish TLS connection to mock server", () => { - const requestsAfter = readCapturedRequests().length; - expect(requestsAfter).toBe(requestsBefore); - }); }); }); diff --git a/apps/login/dockerized/ca/mock-server.ts b/apps/login/dockerized/ca/mock-server.ts deleted file mode 100644 index 21a06dd34..000000000 --- a/apps/login/dockerized/ca/mock-server.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as http2 from "node:http2"; -import * as fs from "node:fs"; - -const OUTPUT_DIR = process.env.OUTPUT_DIR || "/output"; - -const server = http2.createSecureServer({ - key: fs.readFileSync("/certs/server.key"), - cert: fs.readFileSync("/certs/server.crt"), - allowHTTP1: true, -}); - -interface CapturedRequest { - method: string; - url: string; - tlsConnected: boolean; -} - -const requests: CapturedRequest[] = []; - -server.on("stream", (stream, headers) => { - requests.push({ - method: (headers[":method"] as string) || "UNKNOWN", - url: (headers[":path"] as string) || "/", - tlsConnected: true, - }); - fs.writeFileSync(`${OUTPUT_DIR}/requests.json`, JSON.stringify(requests, null, 2)); - - stream.respond({ - ":status": 200, - "content-type": "application/json", - "grpc-status": "0", - }); - stream.end(JSON.stringify({ status: "ok" })); -}); - -server.listen(443, "0.0.0.0", () => { - console.log("Mock TLS server listening on port 443"); -}); diff --git a/apps/login/dockerized/otel/mock-zitadel-server.js b/apps/login/dockerized/otel/mock-zitadel-server.js new file mode 100644 index 000000000..5b32af2dc --- /dev/null +++ b/apps/login/dockerized/otel/mock-zitadel-server.js @@ -0,0 +1,155 @@ +/** + * Mock Zitadel Connect/gRPC server for testing trace propagation. + * Captures incoming headers and writes them to a file for test assertions. + * + * Uses HTTP/2 with cleartext (h2c) to support gRPC protocol. + */ +const http2 = require("http2"); +const http = require("http"); +const fs = require("fs"); +const path = require("path"); + +const PORT = process.env.PORT || 8080; +const OUTPUT_DIR = process.env.OUTPUT_DIR || "/tmp/otel"; +const HEADERS_FILE = path.join(OUTPUT_DIR, "captured-headers.json"); + +/** + * Safely stringify a value to JSON with XSS-preventing character escapes. + * + * Escapes `<`, `>`, `&`, and `/` to their Unicode equivalents to prevent + * script injection when the output is reflected in HTTP responses or + * embedded in HTML contexts. + * + * @param {unknown} value - The value to stringify + * @returns {string} JSON string with dangerous characters escaped + */ +function safeJsonStringify(value) { + return JSON.stringify(value, null, 2) + .replace(//g, "\\u003e") + .replace(/&/g, "\\u0026") + .replace(/\//g, "\\/"); +} + +// Store captured requests +const capturedRequests = []; + +// Handle HTTP/2 request +function handleRequest(stream, headers, isHttp2 = true) { + const method = isHttp2 ? headers[":method"] : headers.method; + const url = isHttp2 ? headers[":path"] : headers.url; + + // Capture trace headers + const traceHeaders = { + timestamp: new Date().toISOString(), + method: method, + url: url, + traceparent: headers["traceparent"] || null, + tracestate: headers["tracestate"] || null, + baggage: headers["baggage"] || null, + grpcAcceptEncoding: headers["grpc-accept-encoding"] || null, + contentType: headers["content-type"] || null, + }; + + capturedRequests.push(traceHeaders); + + // Write to file for test assertions + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + fs.writeFileSync(HEADERS_FILE, JSON.stringify(capturedRequests, null, 2)); + + console.log(`[${traceHeaders.timestamp}] ${method} ${url}`); + console.log(` traceparent: ${traceHeaders.traceparent || "(none)"}`); + + // Helper to send response + const sendResponse = (statusCode, contentType, body) => { + if (isHttp2) { + stream.respond({ + ":status": statusCode, + "content-type": contentType, + "grpc-status": "0", + "grpc-message": "OK", + }); + stream.end(body); + } else { + stream.writeHead(statusCode, { + "Content-Type": contentType, + "grpc-status": "0", + "grpc-message": "OK", + }); + stream.end(body); + } + }; + + // Health check + if (url.includes("/health") || url === "/") { + sendResponse(200, "application/json", safeJsonStringify({ status: "ok" })); + return; + } + + // Return captured headers (for debugging) + if (url === "/captured-headers") { + sendResponse(200, "application/json", safeJsonStringify(capturedRequests)); + return; + } + + // Mock Connect/gRPC responses + // These are minimal responses to prevent the login app from crashing + if (url.includes("GetLoginSettings") || url.includes("SettingsService")) { + sendResponse(200, "application/json", safeJsonStringify({ settings: {} })); + } else if (url.includes("GetBrandingSettings")) { + sendResponse(200, "application/json", safeJsonStringify({ settings: {} })); + } else if (url.includes("SessionService") || url.includes("Session")) { + sendResponse(200, "application/json", safeJsonStringify({ session: {} })); + } else if (url.includes("UserService") || url.includes("User")) { + sendResponse(200, "application/json", safeJsonStringify({ user: {} })); + } else if (url.includes("OrganizationService") || url.includes("Organization")) { + sendResponse(200, "application/json", safeJsonStringify({ result: [] })); + } else if (url.includes("IdentityProviderService") || url.includes("IdentityProvider")) { + sendResponse(200, "application/json", safeJsonStringify({ identityProviders: [] })); + } else { + sendResponse(200, "application/json", safeJsonStringify({})); + } +} + +// Create HTTP/2 server (cleartext for testing) +const http2Server = http2.createServer(); + +http2Server.on("stream", (stream, headers) => { + handleRequest(stream, headers, true); +}); + +http2Server.on("error", (err) => console.error("HTTP/2 error:", err)); + +// Also create HTTP/1.1 server for health checks from wget +const httpServer = http.createServer((req, res) => { + // Create a headers object compatible with handleRequest + const headers = { + method: req.method, + url: req.url, + ...req.headers, + }; + handleRequest(res, headers, false); +}); + +// Start HTTP/2 server on main port +http2Server.listen(PORT, "0.0.0.0", () => { + console.log(`Mock Zitadel HTTP/2 server listening on port ${PORT}`); + console.log(`Captured headers will be written to: ${HEADERS_FILE}`); +}); + +// Start HTTP/1.1 server on a different port for health checks +const HEALTH_PORT = 7432; +httpServer.listen(HEALTH_PORT, "0.0.0.0", () => { + console.log(`Mock Zitadel HTTP/1.1 health server listening on port ${HEALTH_PORT}`); +}); + +// Graceful shutdown +process.on("SIGTERM", () => { + console.log("Shutting down mock servers..."); + http2Server.close(() => { + httpServer.close(() => { + console.log("Mock servers stopped"); + process.exit(0); + }); + }); +}); diff --git a/apps/login/dockerized/otel/otel-collector-config.yaml b/apps/login/dockerized/otel/otel-collector-config.yaml new file mode 100644 index 000000000..550c6bf37 --- /dev/null +++ b/apps/login/dockerized/otel/otel-collector-config.yaml @@ -0,0 +1,43 @@ +receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + timeout: 1s + send_batch_size: 1024 + +exporters: + debug: + verbosity: detailed + file/traces: + path: /tmp/otel/traces.json + flush_interval: 1s + file/metrics: + path: /tmp/otel/metrics.json + flush_interval: 1s + file/logs: + path: /tmp/otel/logs.json + flush_interval: 1s + +extensions: + health_check: + endpoint: 0.0.0.0:13133 + +service: + extensions: [health_check] + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [debug, file/traces] + metrics: + receivers: [otlp] + processors: [batch] + exporters: [debug, file/metrics] + logs: + receivers: [otlp] + processors: [batch] + exporters: [debug, file/logs] diff --git a/apps/login/dockerized/otel/otel.integration.test.ts b/apps/login/dockerized/otel/otel.integration.test.ts new file mode 100644 index 000000000..2eec51d81 --- /dev/null +++ b/apps/login/dockerized/otel/otel.integration.test.ts @@ -0,0 +1,1205 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { GenericContainer, type StartedTestContainer, Network, Wait } from "testcontainers"; +import type { StartedNetwork } from "testcontainers"; +import * as fs from "fs"; +import * as path from "path"; + +const TEST_DIR = path.dirname(new URL(import.meta.url).pathname); +const OUTPUT_DIR = path.join(TEST_DIR, "output"); +const LOGIN_APP_DIR = path.join(TEST_DIR, "../.."); + +const DOCKER_TIMEOUT = 180000; +const TEST_TIMEOUT = 30000; + +const LOGIN_IMAGE_TAG = `zitadel-login-test:${process.env.GITHUB_SHA || "local"}`; + +function readTraces(): string { + try { + return fs.readFileSync(path.join(OUTPUT_DIR, "traces.json"), "utf-8"); + } catch { + return ""; + } +} + +function readMetrics(): string { + try { + return fs.readFileSync(path.join(OUTPUT_DIR, "metrics.json"), "utf-8"); + } catch { + return ""; + } +} + +function readLogs(): string { + try { + return fs.readFileSync(path.join(OUTPUT_DIR, "logs.json"), "utf-8"); + } catch { + return ""; + } +} + +function readCapturedHeaders(): Array<{ traceparent: string | null; url: string; method: string }> { + try { + return JSON.parse(fs.readFileSync(path.join(OUTPUT_DIR, "captured-headers.json"), "utf-8")); + } catch { + return []; + } +} + +async function waitForFile(filePath: string, maxAttempts = 30, delayMs = 1000): Promise { + for (let i = 0; i < maxAttempts; i++) { + try { + const stat = fs.statSync(filePath); + if (stat.size > 0) { + console.log(`[waitForFile] ${path.basename(filePath)} ready (${stat.size} bytes, attempt ${i + 1})`); + return true; + } + console.log(`[waitForFile] ${path.basename(filePath)} exists but empty (attempt ${i + 1})`); + } catch { + if (i === 0 || i % 10 === 0) { + console.log(`[waitForFile] ${path.basename(filePath)} not found (attempt ${i + 1})`); + } + } + await new Promise((r) => setTimeout(r, delayMs)); + } + console.log(`[waitForFile] ${path.basename(filePath)} timed out after ${maxAttempts} attempts`); + return false; +} + +async function getContainerLogs(container: StartedTestContainer, timeoutMs = 2000): Promise { + const stream = await container.logs(); + return new Promise((resolve) => { + let output = ""; + const timeout = setTimeout(() => { + stream.destroy(); + resolve(output); + }, timeoutMs); + stream.on("data", (chunk: Buffer) => { + output += chunk.toString(); + }); + stream.on("end", () => { + clearTimeout(timeout); + resolve(output); + }); + stream.on("error", () => { + clearTimeout(timeout); + resolve(output); + }); + }); +} + +describe("OpenTelemetry Integration", () => { + let network: StartedNetwork; + let otelCollector: StartedTestContainer; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + let prometheusUrl: string; + + beforeAll(async () => { + if (fs.existsSync(OUTPUT_DIR)) { + fs.rmSync(OUTPUT_DIR, { recursive: true }); + } + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + + fs.writeFileSync(path.join(OUTPUT_DIR, "traces.json"), ""); + fs.writeFileSync(path.join(OUTPUT_DIR, "logs.json"), ""); + fs.writeFileSync(path.join(OUTPUT_DIR, "metrics.json"), ""); + + network = await new Network().start(); + + otelCollector = await new GenericContainer("otel/opentelemetry-collector-contrib:0.145.0") + .withNetwork(network) + .withNetworkAliases("otel-collector") + .withUser("0:0") + .withCommand(["--config=/etc/otel-collector-config.yaml"]) + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "otel-collector-config.yaml"), target: "/etc/otel-collector-config.yaml" }, + ]) + .withBindMounts([{ source: OUTPUT_DIR, target: "/tmp/otel" }]) + .withExposedPorts(4318, 8888, 13133) + .withWaitStrategy(Wait.forHttp("/", 13133)) + .start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withBindMounts([{ source: OUTPUT_DIR, target: "/tmp/otel" }]) + .withEnvironment({ + PORT: "8080", + OUTPUT_DIR: "/tmp/otel", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + try { + await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(LOGIN_IMAGE_TAG); + } catch (err) { + throw new Error(`Failed to build login Docker image: ${err instanceof Error ? err.message : err}`); + } + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000, 9464) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token-for-otel-integration-tests", + OTEL_SERVICE_NAME: "zitadel-login-test", + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318", + OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf", + OTEL_EXPORTER_OTLP_HEADERS: "x-test-header=test-value-123,x-another-header=another-value", + OTEL_LOG_LEVEL: "info", + OTEL_METRICS_EXPORTER: "otlp,prometheus", + OTEL_LOGS_EXPORTER: "otlp", + OTEL_EXPORTER_PROMETHEUS_PORT: "9464", + OTEL_BSP_SCHEDULE_DELAY: "1000", + OTEL_METRIC_EXPORT_INTERVAL: "5000", + DEBUG: "true", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + prometheusUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(9464)}/metrics`; + + console.log(`[PORTS] APP_URL: ${appUrl}`); + console.log(`[PORTS] PROMETHEUS_URL: ${prometheusUrl}`); + + await fetch(`${appUrl}/ui/v2/login/otel-test`); + await fetch(`${appUrl}/ui/v2/login/loginname`, { redirect: "manual" }); + await fetch(`${appUrl}/ui/v2/login/healthy`); + await fetch(`${appUrl}/ui/v2/login/this-does-not-exist-404`); + + await new Promise((r) => setTimeout(r, 3000)); + + const tracesReady = await waitForFile(path.join(OUTPUT_DIR, "traces.json"), 60, 1000); + if (!tracesReady) throw new Error("Traces file not found"); + const logsReady = await waitForFile(path.join(OUTPUT_DIR, "logs.json"), 60, 1000); + if (!logsReady) throw new Error("Logs file not found"); + const metricsReady = await waitForFile(path.join(OUTPUT_DIR, "metrics.json"), 60, 1000); + if (!metricsReady) throw new Error("Metrics file not found"); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await otelCollector?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + describe("Traces", () => { + describe("OTLP Export", () => { + it("exports spans to collector", () => { + expect(readTraces().length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("contains valid trace IDs", () => { + expect(readTraces()).toMatch(/"traceId":"[a-f0-9]{32}"/); + }, TEST_TIMEOUT); + + it("contains valid span IDs", () => { + expect(readTraces()).toMatch(/"spanId":"[a-f0-9]{16}"/); + }, TEST_TIMEOUT); + + it("contains start timestamps", () => { + expect(readTraces()).toContain("startTimeUnixNano"); + }, TEST_TIMEOUT); + + it("contains end timestamps", () => { + expect(readTraces()).toContain("endTimeUnixNano"); + }, TEST_TIMEOUT); + + it("contains span status", () => { + expect(readTraces()).toContain('"status"'); + }, TEST_TIMEOUT); + + it("contains span kind", () => { + expect(readTraces()).toContain('"kind"'); + }, TEST_TIMEOUT); + + it("contains custom test spans", () => { + expect(readTraces()).toContain("test-span"); + }, TEST_TIMEOUT); + }); + + describe("Span Attributes", () => { + it("contains http.method", () => { + expect(readTraces()).toContain('"http.method"'); + }, TEST_TIMEOUT); + + it("contains http.url", () => { + expect(readTraces()).toContain('"http.url"'); + }, TEST_TIMEOUT); + + it("contains http.status_code", () => { + expect(readTraces()).toContain('"http.status_code"'); + }, TEST_TIMEOUT); + + it("contains http.target", () => { + expect(readTraces()).toContain('"http.target"'); + }, TEST_TIMEOUT); + + it("contains net.host.name", () => { + expect(readTraces()).toContain('"net.host.name"'); + }, TEST_TIMEOUT); + + it("contains net.host.port", () => { + expect(readTraces()).toContain('"net.host.port"'); + }, TEST_TIMEOUT); + + it("contains custom test.attribute", () => { + expect(readTraces()).toContain('"test.attribute"'); + }, TEST_TIMEOUT); + + it("contains custom test.timestamp", () => { + expect(readTraces()).toContain('"test.timestamp"'); + }, TEST_TIMEOUT); + }); + + describe("Span Status", () => { + it("contains status code OK", () => { + expect(readTraces()).toContain('"code":1'); + }, TEST_TIMEOUT); + + it("contains status code ERROR", () => { + expect(readTraces()).toContain('"code":2'); + }, TEST_TIMEOUT); + + it("contains error message on failed spans", () => { + expect(readTraces()).toContain('"message"'); + }, TEST_TIMEOUT); + }); + + describe("Span Hierarchy", () => { + it("contains parent span IDs", () => { + expect(readTraces()).toContain('"parentSpanId"'); + }, TEST_TIMEOUT); + + it("has child spans linked to parent", () => { + const traces = readTraces(); + const parentMatches = traces.match(/"parentSpanId":"([a-f0-9]{16})"/g) || []; + expect(parentMatches.length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + }); + + describe("Span Filtering", () => { + it("HTTP instrumentation excludes /healthy", () => { + const tracesContent = readTraces(); + const traceObjects = tracesContent + .split("\n") + .filter((line) => line.trim()) + .map((line) => JSON.parse(line)); + + const allResourceSpans = traceObjects.flatMap((t) => t.resourceSpans || []); + + const httpSpans = allResourceSpans + .flatMap((rs: { scopeSpans: Array<{ scope: { name: string }; spans: unknown[] }> }) => rs.scopeSpans) + .filter((ss: { scope: { name: string } }) => ss.scope.name === "@opentelemetry/instrumentation-http") + .flatMap((ss: { spans: unknown[] }) => ss.spans); + const healthySpans = httpSpans.filter( + (s: { attributes?: Array<{ key: string; value: { stringValue?: string } }> }) => + s.attributes?.some( + (a: { key: string; value: { stringValue?: string } }) => + a.key === "http.target" && a.value.stringValue?.includes("/healthy"), + ), + ); + expect(healthySpans).toHaveLength(0); + }, TEST_TIMEOUT); + + it("includes /otel-test in traces", () => { + const traces = readTraces(); + expect(traces).toContain("otel-test"); + }, TEST_TIMEOUT); + + it("includes /loginname in traces", () => { + const traces = readTraces(); + expect(traces).toContain("loginname"); + }, TEST_TIMEOUT); + }); + + describe("Span Events", () => { + it("contains exception events", () => { + expect(readTraces()).toContain('"name":"exception"'); + }, TEST_TIMEOUT); + + it("contains exception.type attribute", () => { + expect(readTraces()).toContain('"exception.type"'); + }, TEST_TIMEOUT); + + it("contains exception.message attribute", () => { + expect(readTraces()).toContain('"exception.message"'); + }, TEST_TIMEOUT); + + it("contains exception.stacktrace attribute", () => { + expect(readTraces()).toContain('"exception.stacktrace"'); + }, TEST_TIMEOUT); + }); + + describe("gRPC Spans", () => { + it("contains rpc.system attribute", () => { + expect(readTraces()).toContain('"rpc.system"'); + }, TEST_TIMEOUT); + + it("contains rpc.service attribute", () => { + expect(readTraces()).toContain('"rpc.service"'); + }, TEST_TIMEOUT); + + it("contains rpc.method attribute", () => { + expect(readTraces()).toContain('"rpc.method"'); + }, TEST_TIMEOUT); + + it("contains server.address attribute", () => { + expect(readTraces()).toContain('"server.address"'); + }, TEST_TIMEOUT); + + it("contains rpc.grpc.status_code attribute", () => { + expect(readTraces()).toContain('"rpc.grpc.status_code"'); + }, TEST_TIMEOUT); + + it("identifies grpc as rpc system", () => { + expect(readTraces()).toContain('"grpc"'); + }, TEST_TIMEOUT); + }); + + describe("HTTP Status Codes", () => { + it("records HTTP 200 status", () => { + expect(readTraces()).toContain('"intValue":"200"'); + }, TEST_TIMEOUT); + + it("records HTTP 404 status", () => { + expect(readTraces()).toContain('"intValue":"404"'); + }, TEST_TIMEOUT); + + it("records OK status text", () => { + expect(readTraces()).toContain('"OK"'); + }, TEST_TIMEOUT); + + it("records NOT FOUND status text", () => { + expect(readTraces()).toContain('"NOT FOUND"'); + }, TEST_TIMEOUT); + }); + + describe("Trace Propagation", () => { + it("propagates traceparent to gRPC", () => { + const requests = readCapturedHeaders(); + const grpcCalls = requests.filter((r) => r.method === "POST" && r.traceparent !== null); + expect(grpcCalls.length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("uses W3C traceparent format", () => { + const requests = readCapturedHeaders(); + const grpcCalls = requests.filter((r) => r.traceparent !== null); + grpcCalls.forEach((r) => expect(r.traceparent).toMatch(/^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/)); + }, TEST_TIMEOUT); + + it("uses traceparent version 00", () => { + const requests = readCapturedHeaders(); + const grpcCalls = requests.filter((r) => r.traceparent !== null); + grpcCalls.forEach((r) => expect(r.traceparent).toMatch(/^00-/)); + }, TEST_TIMEOUT); + + it("sets sampled flag to 01", () => { + const requests = readCapturedHeaders(); + const grpcCalls = requests.filter((r) => r.traceparent !== null); + grpcCalls.forEach((r) => expect(r.traceparent).toMatch(/-01$/)); + }, TEST_TIMEOUT); + + it("maintains trace ID consistency", () => { + const requests = readCapturedHeaders(); + const grpcCalls = requests.filter((r) => r.method === "POST" && r.traceparent !== null); + const traceIds = grpcCalls.map((r) => r.traceparent!.split("-")[1]); + const uniqueIds = new Set(traceIds); + expect(uniqueIds.size).toBeLessThan(traceIds.length); + }, TEST_TIMEOUT); + }); + + describe("Resource Attributes", () => { + it("contains service.name", () => { + expect(readTraces()).toContain('"zitadel-login-test"'); + }, TEST_TIMEOUT); + + it("contains deployment.environment.name", () => { + expect(readTraces()).toContain('"deployment.environment.name"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.name", () => { + expect(readTraces()).toContain('"nodejs"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.version", () => { + expect(readTraces()).toContain('"process.runtime.version"'); + }, TEST_TIMEOUT); + + it("contains container.id when running in container", () => { + expect(readTraces()).toContain('"container.id"'); + }, TEST_TIMEOUT); + + it("contains host.name", () => { + expect(readTraces()).toContain('"host.name"'); + }, TEST_TIMEOUT); + + it("contains host.arch", () => { + expect(readTraces()).toContain('"host.arch"'); + }, TEST_TIMEOUT); + + it("contains process.pid", () => { + expect(readTraces()).toContain('"process.pid"'); + }, TEST_TIMEOUT); + }); + }); + + describe("Metrics", () => { + describe("OTLP Export", () => { + it("exports metrics to collector", () => { + expect(readMetrics().length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("contains timestamps", () => { + expect(readMetrics()).toContain("timeUnixNano"); + }, TEST_TIMEOUT); + + it("contains data points", () => { + expect(readMetrics()).toContain("dataPoints"); + }, TEST_TIMEOUT); + }); + + describe("Prometheus Endpoint", () => { + it("returns HTTP 200", async () => { + const response = await fetch(prometheusUrl); + expect(response.status).toBe(200); + }, TEST_TIMEOUT); + + it("returns text/plain content type", async () => { + const response = await fetch(prometheusUrl); + expect(response.headers.get("content-type")).toContain("text/plain"); + }, TEST_TIMEOUT); + }); + + describe("Runtime Metrics", () => { + it("exposes nodejs_eventloop_utilization", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("nodejs_eventloop_utilization"); + }, TEST_TIMEOUT); + + it("exposes nodejs_eventloop_delay", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("nodejs_eventloop_delay"); + }, TEST_TIMEOUT); + + it("exposes v8js_memory_heap_used", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("v8js_memory_heap_used"); + }, TEST_TIMEOUT); + + it("exposes v8js_memory_heap_limit", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("v8js_memory_heap_limit"); + }, TEST_TIMEOUT); + + it("exposes v8js_gc_duration", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("v8js_gc_duration"); + }, TEST_TIMEOUT); + }); + + describe("HTTP Metrics", () => { + it("exposes http_server_duration", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_duration"); + }, TEST_TIMEOUT); + + it("exposes http_server_duration_sum", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_duration_sum"); + }, TEST_TIMEOUT); + + it("exposes http_server_duration_count", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_duration_count"); + }, TEST_TIMEOUT); + + it("exposes http_server_duration_bucket", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_duration_bucket"); + }, TEST_TIMEOUT); + + it("records status code 200", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('http_status_code="200"'); + }, TEST_TIMEOUT); + + it("records status code 404", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('http_status_code="404"'); + }, TEST_TIMEOUT); + + it("records HTTP GET method", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('http_method="GET"'); + }, TEST_TIMEOUT); + }); + + describe("Application Metrics", () => { + it("exposes http_server_requests_total", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_requests_total"); + }, TEST_TIMEOUT); + + it("exposes http_server_active_requests", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_active_requests"); + }, TEST_TIMEOUT); + + it("exposes login_auth_attempts_total", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("login_auth_attempts_total"); + }, TEST_TIMEOUT); + + it("exposes login_auth_successes_total", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("login_auth_successes_total"); + }, TEST_TIMEOUT); + + it("exposes login_session_creation_duration", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("login_session_creation_duration"); + }, TEST_TIMEOUT); + + it("exposes http_server_response_status_total", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_response_status_total"); + }, TEST_TIMEOUT); + + it("exposes http_server_request_duration_seconds", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("http_server_request_duration_seconds"); + }, TEST_TIMEOUT); + }); + + describe("Custom Test Metrics", () => { + it("exposes otel_test_requests_total", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("otel_test_requests_total"); + }, TEST_TIMEOUT); + + it("exposes otel_test_duration_seconds", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain("otel_test_duration_seconds"); + }, TEST_TIMEOUT); + }); + + describe("Metric Labels", () => { + it("includes method label on auth metrics", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('method="test"'); + }, TEST_TIMEOUT); + + it("includes organization label on auth metrics", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('organization="test-org"'); + }, TEST_TIMEOUT); + + it("includes route label on request metrics", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('route="/otel-test"'); + }, TEST_TIMEOUT); + + it("includes success label on session metrics", async () => { + const response = await fetch(prometheusUrl); + expect(await response.text()).toContain('success="true"'); + }, TEST_TIMEOUT); + }); + + describe("Resource Attributes", () => { + it("contains service.name", () => { + expect(readMetrics()).toContain('"zitadel-login-test"'); + }, TEST_TIMEOUT); + + it("contains deployment.environment.name", () => { + expect(readMetrics()).toContain('"deployment.environment.name"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.name", () => { + expect(readMetrics()).toContain('"nodejs"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.version", () => { + expect(readMetrics()).toContain('"process.runtime.version"'); + }, TEST_TIMEOUT); + + it("contains container.id when running in container", () => { + expect(readMetrics()).toContain('"container.id"'); + }, TEST_TIMEOUT); + + it("contains host.name", () => { + expect(readMetrics()).toContain('"host.name"'); + }, TEST_TIMEOUT); + + it("contains host.arch", () => { + expect(readMetrics()).toContain('"host.arch"'); + }, TEST_TIMEOUT); + + it("contains process.pid", () => { + expect(readMetrics()).toContain('"process.pid"'); + }, TEST_TIMEOUT); + }); + }); + + describe("Logs", () => { + describe("OTLP Export", () => { + it("exports logs to collector", () => { + expect(readLogs().length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("contains timestamps", () => { + expect(readLogs()).toContain("timeUnixNano"); + }, TEST_TIMEOUT); + + it("contains severity", () => { + expect(readLogs()).toContain("severityNumber"); + }, TEST_TIMEOUT); + + it("contains body", () => { + expect(readLogs()).toContain("body"); + }, TEST_TIMEOUT); + }); + + describe("Log Content", () => { + it("contains OTEL test log message", () => { + expect(readLogs()).toContain("OTEL test endpoint called"); + }, TEST_TIMEOUT); + + it("contains test completed log message", () => { + expect(readLogs()).toContain("OTEL test completed successfully"); + }, TEST_TIMEOUT); + + it("contains INFO severity level", () => { + const logs = readLogs(); + expect(logs).toContain('"severityText":"info"'); + }, TEST_TIMEOUT); + + it("contains severity number 9 for INFO", () => { + expect(readLogs()).toContain('"severityNumber":9'); + }, TEST_TIMEOUT); + }); + + describe("Log-Trace Correlation", () => { + it("includes traceId in log records", () => { + expect(readLogs()).toContain('"traceId"'); + }, TEST_TIMEOUT); + + it("includes spanId in log records", () => { + expect(readLogs()).toContain('"spanId"'); + }, TEST_TIMEOUT); + + it("has valid trace ID format", () => { + expect(readLogs()).toMatch(/"traceId":"[a-f0-9]{32}"/); + }, TEST_TIMEOUT); + + it("has valid span ID format", () => { + expect(readLogs()).toMatch(/"spanId":"[a-f0-9]{16}"/); + }, TEST_TIMEOUT); + + it("includes trace_id attribute", () => { + expect(readLogs()).toContain('"trace_id"'); + }, TEST_TIMEOUT); + + it("includes span_id attribute", () => { + expect(readLogs()).toContain('"span_id"'); + }, TEST_TIMEOUT); + + it("includes trace_flags attribute", () => { + expect(readLogs()).toContain('"trace_flags"'); + }, TEST_TIMEOUT); + }); + + describe("Log Attributes", () => { + it("includes context attribute", () => { + expect(readLogs()).toContain('"context"'); + }, TEST_TIMEOUT); + + it("includes service attribute", () => { + expect(readLogs()).toContain('"service"'); + }, TEST_TIMEOUT); + + it("includes timestamp attribute", () => { + expect(readLogs()).toContain('"timestamp"'); + }, TEST_TIMEOUT); + + it("includes custom test attribute", () => { + expect(readLogs()).toContain('"test"'); + }, TEST_TIMEOUT); + + it("includes duration attribute", () => { + expect(readLogs()).toContain('"duration"'); + }, TEST_TIMEOUT); + }); + + describe("Resource Attributes", () => { + it("contains service.name", () => { + expect(readLogs()).toContain('"zitadel-login-test"'); + }, TEST_TIMEOUT); + + it("contains deployment.environment.name", () => { + expect(readLogs()).toContain('"deployment.environment.name"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.name", () => { + expect(readLogs()).toContain('"nodejs"'); + }, TEST_TIMEOUT); + + it("contains process.runtime.version", () => { + expect(readLogs()).toContain('"process.runtime.version"'); + }, TEST_TIMEOUT); + + it("contains container.id when running in container", () => { + expect(readLogs()).toContain('"container.id"'); + }, TEST_TIMEOUT); + + it("contains host.name", () => { + expect(readLogs()).toContain('"host.name"'); + }, TEST_TIMEOUT); + + it("contains host.arch", () => { + expect(readLogs()).toContain('"host.arch"'); + }, TEST_TIMEOUT); + + it("contains process.pid", () => { + expect(readLogs()).toContain('"process.pid"'); + }, TEST_TIMEOUT); + }); + }); +}); + +describe("OpenTelemetry Disabled", () => { + let network: StartedNetwork; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + + beforeAll(async () => { + network = await new Network().start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withEnvironment({ + PORT: "8080", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000, 9464) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token", + OTEL_SDK_DISABLED: "true", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + console.log(`[OTEL DISABLED] APP_URL: ${appUrl}`); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + describe("Application Health", () => { + it("starts successfully with OTEL_SDK_DISABLED=true", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.ok).toBe(true); + }, TEST_TIMEOUT); + + it("responds to health checks", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.status).toBe(200); + }, TEST_TIMEOUT); + }); + + describe("Metrics Endpoint", () => { + it("metrics endpoint is not available when OTEL is disabled", async () => { + const metricsUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(9464)}/metrics`; + try { + await fetch(metricsUrl); + expect.fail("Metrics endpoint should not be available"); + } catch (error) { + expect(error).toBeDefined(); + } + }, TEST_TIMEOUT); + }); + + describe("Logging", () => { + it("outputs logs to console via Winston", async () => { + await fetch(`${appUrl}/ui/v2/login/healthy`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const logOutput = await getContainerLogs(loginApp); + expect(logOutput.length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("does not contain OTEL SDK initialization logs", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).not.toContain("OpenTelemetry SDK started"); + expect(logOutput).not.toContain("Exporter"); + }, TEST_TIMEOUT); + }); +}); + +describe("Log Level Configuration", () => { + describe("OTEL Disabled", () => { + describe("LOG_LEVEL=debug", () => { + let network: StartedNetwork; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + + beforeAll(async () => { + network = await new Network().start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withEnvironment({ + PORT: "8080", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token", + OTEL_SDK_DISABLED: "true", + LOG_LEVEL: "debug", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + console.log(`[OTEL_DISABLED + LOG_LEVEL=debug] APP_URL: ${appUrl}`); + + await fetch(`${appUrl}/ui/v2/login/healthy`); + await fetch(`${appUrl}/ui/v2/login/otel-test`); + await new Promise((r) => setTimeout(r, 1000)); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + it("starts successfully", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.ok).toBe(true); + }, TEST_TIMEOUT); + + it("includes info level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).toContain('"level":"info"'); + }, TEST_TIMEOUT); + }); + + describe("LOG_LEVEL=warn", () => { + let network: StartedNetwork; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + + beforeAll(async () => { + network = await new Network().start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withEnvironment({ + PORT: "8080", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token", + OTEL_SDK_DISABLED: "true", + LOG_LEVEL: "warn", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + console.log(`[OTEL_DISABLED + LOG_LEVEL=warn] APP_URL: ${appUrl}`); + + await fetch(`${appUrl}/ui/v2/login/healthy`); + await new Promise((r) => setTimeout(r, 1000)); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + it("starts successfully", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.ok).toBe(true); + }, TEST_TIMEOUT); + + it("does not include info level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).not.toContain('"level":"info"'); + }, TEST_TIMEOUT); + + it("does not include debug level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).not.toContain('"level":"debug"'); + }, TEST_TIMEOUT); + }); + }); + + describe("OTEL Enabled", () => { + const OTEL_LOG_OUTPUT_DIR = path.join(OUTPUT_DIR, "loglevel"); + + describe("LOG_LEVEL=debug", () => { + let network: StartedNetwork; + let otelCollector: StartedTestContainer; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + let logOutputDir: string; + + beforeAll(async () => { + logOutputDir = path.join(OTEL_LOG_OUTPUT_DIR, "debug"); + if (fs.existsSync(logOutputDir)) { + fs.rmSync(logOutputDir, { recursive: true }); + } + fs.mkdirSync(logOutputDir, { recursive: true }); + fs.writeFileSync(path.join(logOutputDir, "logs.json"), ""); + + network = await new Network().start(); + + otelCollector = await new GenericContainer("otel/opentelemetry-collector-contrib:0.145.0") + .withNetwork(network) + .withNetworkAliases("otel-collector") + .withUser("0:0") + .withCommand(["--config=/etc/otel-collector-config.yaml"]) + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "otel-collector-config.yaml"), target: "/etc/otel-collector-config.yaml" }, + ]) + .withBindMounts([{ source: logOutputDir, target: "/tmp/otel" }]) + .withExposedPorts(4318, 13133) + .withWaitStrategy(Wait.forHttp("/", 13133)) + .start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withEnvironment({ + PORT: "8080", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token", + OTEL_SERVICE_NAME: "zitadel-login-loglevel-debug", + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318", + OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf", + OTEL_LOG_LEVEL: "debug", + OTEL_LOGS_EXPORTER: "otlp", + OTEL_BSP_SCHEDULE_DELAY: "1000", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + console.log(`[OTEL_ENABLED + LOG_LEVEL=debug] APP_URL: ${appUrl}`); + + await fetch(`${appUrl}/ui/v2/login/healthy`); + await fetch(`${appUrl}/ui/v2/login/otel-test`); + await new Promise((r) => setTimeout(r, 3000)); + + await waitForFile(path.join(logOutputDir, "logs.json"), 30, 1000); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await otelCollector?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + it("starts successfully", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.ok).toBe(true); + }, TEST_TIMEOUT); + + it("exports logs to OTLP collector", () => { + const logs = fs.readFileSync(path.join(logOutputDir, "logs.json"), "utf-8"); + expect(logs.length).toBeGreaterThan(0); + }, TEST_TIMEOUT); + + it("includes info level logs in OTLP export", () => { + const logs = fs.readFileSync(path.join(logOutputDir, "logs.json"), "utf-8"); + expect(logs).toContain('"severityText":"info"'); + }, TEST_TIMEOUT); + + it("includes info level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).toContain('"level":"info"'); + }, TEST_TIMEOUT); + }); + + describe("LOG_LEVEL=warn", () => { + let network: StartedNetwork; + let otelCollector: StartedTestContainer; + let mockZitadel: StartedTestContainer; + let loginApp: StartedTestContainer; + let appUrl: string; + let logOutputDir: string; + + beforeAll(async () => { + logOutputDir = path.join(OTEL_LOG_OUTPUT_DIR, "warn"); + if (fs.existsSync(logOutputDir)) { + fs.rmSync(logOutputDir, { recursive: true }); + } + fs.mkdirSync(logOutputDir, { recursive: true }); + fs.writeFileSync(path.join(logOutputDir, "logs.json"), ""); + + network = await new Network().start(); + + otelCollector = await new GenericContainer("otel/opentelemetry-collector-contrib:0.145.0") + .withNetwork(network) + .withNetworkAliases("otel-collector") + .withUser("0:0") + .withCommand(["--config=/etc/otel-collector-config.yaml"]) + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "otel-collector-config.yaml"), target: "/etc/otel-collector-config.yaml" }, + ]) + .withBindMounts([{ source: logOutputDir, target: "/tmp/otel" }]) + .withExposedPorts(4318, 13133) + .withWaitStrategy(Wait.forHttp("/", 13133)) + .start(); + + mockZitadel = await new GenericContainer("node:20-alpine") + .withNetwork(network) + .withNetworkAliases("mock-zitadel") + .withCopyFilesToContainer([ + { source: path.join(TEST_DIR, "mock-zitadel-server.js"), target: "/app/server.js" }, + ]) + .withEnvironment({ + PORT: "8080", + }) + .withCommand(["node", "/app/server.js"]) + .withExposedPorts(8080, 7432) + .withWaitStrategy(Wait.forLogMessage("Mock Zitadel HTTP/2 server listening")) + .start(); + + loginApp = await new GenericContainer(LOGIN_IMAGE_TAG) + .withNetwork(network) + .withExposedPorts(3000) + .withEnvironment({ + ZITADEL_API_URL: "http://mock-zitadel:8080", + ZITADEL_SERVICE_USER_TOKEN: "test-token", + OTEL_SERVICE_NAME: "zitadel-login-loglevel-warn", + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector:4318", + OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf", + OTEL_LOG_LEVEL: "warn", + OTEL_LOGS_EXPORTER: "otlp", + OTEL_BSP_SCHEDULE_DELAY: "1000", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forHttp("/ui/v2/login/healthy", 3000)) + .start(); + + appUrl = `http://${loginApp.getHost()}:${loginApp.getMappedPort(3000)}`; + console.log(`[OTEL_ENABLED + LOG_LEVEL=warn] APP_URL: ${appUrl}`); + + await fetch(`${appUrl}/ui/v2/login/healthy`); + await fetch(`${appUrl}/ui/v2/login/otel-test`); + await new Promise((r) => setTimeout(r, 3000)); + }, DOCKER_TIMEOUT); + + afterAll(async () => { + await loginApp?.stop().catch(() => {}); + await mockZitadel?.stop().catch(() => {}); + await otelCollector?.stop().catch(() => {}); + await network?.stop().catch(() => {}); + }, DOCKER_TIMEOUT); + + it("starts successfully", async () => { + const response = await fetch(`${appUrl}/ui/v2/login/healthy`); + expect(response.ok).toBe(true); + }, TEST_TIMEOUT); + + it("does not include info level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).not.toContain('"level":"info"'); + }, TEST_TIMEOUT); + + it("does not include debug level logs in console", async () => { + const logOutput = await getContainerLogs(loginApp); + expect(logOutput).not.toContain('"level":"debug"'); + }, TEST_TIMEOUT); + + it("does not export info level logs to OTLP", () => { + const logsPath = path.join(logOutputDir, "logs.json"); + if (fs.existsSync(logsPath)) { + const logs = fs.readFileSync(logsPath, "utf-8"); + expect(logs).not.toContain('"severityText":"info"'); + } + }, TEST_TIMEOUT); + + it("does not export debug level logs to OTLP", () => { + const logsPath = path.join(logOutputDir, "logs.json"); + if (fs.existsSync(logsPath)) { + const logs = fs.readFileSync(logsPath, "utf-8"); + expect(logs).not.toContain('"severityText":"debug"'); + } + }, TEST_TIMEOUT); + }); + }); +}); diff --git a/apps/login/dockerized/ssl/tls.integration.test.ts b/apps/login/dockerized/ssl/tls.integration.test.ts index dafec9e92..4dc6ab004 100644 --- a/apps/login/dockerized/ssl/tls.integration.test.ts +++ b/apps/login/dockerized/ssl/tls.integration.test.ts @@ -10,6 +10,7 @@ const TEST_DIR = path.dirname(fileURLToPath(import.meta.url)); const OUTPUT_DIR = path.join(TEST_DIR, "output"); const CERTS_DIR = path.join(OUTPUT_DIR, "certs"); const LOGIN_APP_DIR = path.join(TEST_DIR, "../.."); +const LOGIN_IMAGE_TAG = `zitadel-login-test:${process.env.GITHUB_SHA || "local"}`; /** * Integration tests for TLS support in the login application. @@ -44,7 +45,8 @@ describe("Login Container TLS Support", () => { fs.writeFileSync(path.join(CERTS_DIR, "server.crt"), certs.server.cert); fs.writeFileSync(path.join(CERTS_DIR, "server.key"), certs.server.key); - loginImage = await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(); + await GenericContainer.fromDockerfile(LOGIN_APP_DIR).build(LOGIN_IMAGE_TAG); + loginImage = new GenericContainer(LOGIN_IMAGE_TAG); }); describe("when TLS is not enabled", () => { diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index 6e280d615..a1533d03b 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -42,7 +42,27 @@ const nextConfig = { : {}), }, }, - + // Packages that must not be bundled by webpack and should remain as external + // requires at runtime. These packages use native modules or have bundling + // incompatibilities. Keep this list in sync with package.json dependencies + // when adding new OpenTelemetry or logging packages. + serverExternalPackages: [ + 'winston', + '@opentelemetry/api', + '@opentelemetry/api-logs', + '@opentelemetry/sdk-node', + '@opentelemetry/sdk-metrics', + '@opentelemetry/sdk-logs', + '@opentelemetry/exporter-metrics-otlp-http', + '@opentelemetry/exporter-logs-otlp-http', + '@opentelemetry/exporter-prometheus', + '@opentelemetry/resources', + '@opentelemetry/semantic-conventions', + '@opentelemetry/auto-instrumentations-node', + '@opentelemetry/winston-transport', + '@opentelemetry/resource-detector-container', + '@opentelemetry/resource-detector-gcp', + ], // Improve SSR stability - not actually needed for React 19 SSR issues // onDemandEntries: { // maxInactiveAge: 25 * 1000, diff --git a/apps/login/package.json b/apps/login/package.json index 454c66192..407990488 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -12,6 +12,7 @@ "lint-check-prettier": "prettier --check .", "lint-fix": "prettier --write .", "test-unit": "vitest --run", + "test-dockerized": "vitest --run --config vitest.config.dockerized.mts", "lint-staged": "lint-staged", "clean": "rm -rf node_modules .next cypress" }, @@ -24,6 +25,14 @@ "dependencies": { "@headlessui/react": "^2.2.9", "@heroicons/react": "^2.2.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.211.0", + "@opentelemetry/auto-instrumentations-node": "^0.69.0", + "@opentelemetry/resource-detector-container": "^0.8.2", + "@opentelemetry/resource-detector-gcp": "^0.46.0", + "@opentelemetry/resources": "^2.5.0", + "@opentelemetry/sdk-node": "^0.211.0", + "@opentelemetry/winston-transport": "^0.21.0", "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/forms": "^0.5.11", "clsx": "^2.1.1", @@ -40,21 +49,25 @@ "react": "19.2.4", "react-dom": "19.2.4", "react-hook-form": "^7.71.1", + "server-only": "^0.0.1", "tinycolor2": "^1.6.0", - "uuid": "^13.0.0" + "uuid": "^13.0.0", + "winston": "^3.17.0" }, "devDependencies": { "@babel/eslint-parser": "^7.28.6", "@faker-js/faker": "^9.9.0", + "@opentelemetry/sdk-trace-base": "^2.5.0", + "@opentelemetry/sdk-trace-node": "^2.5.0", "@otplib/core": "^12.0.1", "@otplib/plugin-crypto": "^12.0.1", "@otplib/plugin-thirty-two": "^12.0.1", "@playwright/test": "^1.58.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", + "@types/escape-html": "^1.0.4", "@types/node": "^22.19.7", "@types/node-forge": "^1.3.14", - "@types/escape-html": "^1.0.4", "@types/react": "19.2.7", "@types/react-dom": "19.2.3", "@types/tinycolor2": "^1.4.6", @@ -74,6 +87,7 @@ "eslint": "^8.57.1", "eslint-config-next": "15.5.9", "eslint-config-prettier": "^9.1.2", + "eslint-plugin-import": "^2.32.0", "gaxios": "^7.1.3", "grpc-tools": "^1.13.1", "jsdom": "^26.1.0", diff --git a/apps/login/project.json b/apps/login/project.json index 814bcd3cc..ad56b84f2 100644 --- a/apps/login/project.json +++ b/apps/login/project.json @@ -99,10 +99,11 @@ ] }, "test": { - "description": "Runs all tests (unit and integration)", + "description": "Runs all tests (unit, integration, and dockerized)", "dependsOn": [ "test-unit", - "test-integration" + "test-integration", + "test-dockerized" ] }, "test-unit": { @@ -111,10 +112,11 @@ "^build" ] }, - "test-ca": { - "description": "Runs custom CA certificate integration tests using testcontainers", + "test-dockerized": { + "description": "Runs dockerized integration tests (OpenTelemetry and custom CA certificate tests)", "dependsOn": [ - "build" + "build", + "test-integration" ], "executor": "nx:run-commands", "cache": true, @@ -127,7 +129,7 @@ "options": { "cwd": "{projectRoot}", "commands": [ - "pnpm vitest --run --config vitest.config.ca.mts" + "pnpm vitest --run --config vitest.config.dockerized.mts" ] } }, diff --git a/apps/login/scripts/entrypoint.sh b/apps/login/scripts/entrypoint.sh index f16d5ed50..2da2aca45 100755 --- a/apps/login/scripts/entrypoint.sh +++ b/apps/login/scripts/entrypoint.sh @@ -15,4 +15,9 @@ if [ -n "${ZITADEL_SERVICE_USER_TOKEN_FILE}" ]; then export ZITADEL_SERVICE_USER_TOKEN=$(cat "${ZITADEL_SERVICE_USER_TOKEN_FILE}") fi +# Log OTEL configuration on startup (if enabled) +if [ -n "${OTEL_EXPORTER_OTLP_ENDPOINT}" ]; then + echo "OTEL enabled: service=${OTEL_SERVICE_NAME:-zitadel-login}, endpoint=${OTEL_EXPORTER_OTLP_ENDPOINT}" +fi + exec $@ diff --git a/apps/login/scripts/server.mjs b/apps/login/scripts/server.mjs index 2dfb7125a..4f4a9a47a 100644 --- a/apps/login/scripts/server.mjs +++ b/apps/login/scripts/server.mjs @@ -154,7 +154,8 @@ if (process.env.ZITADEL_TLS_ENABLED !== "true") { } httpsServer.listen(currentPort, hostname, () => { - const readyUrl = new URL(`https://${hostname}:${currentPort}`); + const formattedHost = hostname.includes(":") ? `[${hostname}]` : hostname; + const readyUrl = new URL(`https://${formattedHost}:${currentPort}`); console.log(`> Ready on ${readyUrl}`); }); } diff --git a/apps/login/src/app/otel-test/route.ts b/apps/login/src/app/otel-test/route.ts new file mode 100644 index 000000000..dc39c7fdb --- /dev/null +++ b/apps/login/src/app/otel-test/route.ts @@ -0,0 +1,85 @@ +import { NextResponse } from "next/server"; +import { trace, metrics, SpanStatusCode } from "@opentelemetry/api"; +import { createLogger } from "@/lib/logger"; +import { + recordAuthAttempt, + recordAuthSuccess, + recordAuthFailure, + recordRequestStart, + recordRequestEnd, + recordSessionCreationDuration, +} from "@/lib/metrics"; + +export const runtime = "nodejs"; +const logger = createLogger("otel-test"); + +export async function GET() { + const startTime = Date.now(); + + recordRequestStart(); + recordAuthAttempt("test", "test-org"); + + const tracer = trace.getTracer("otel-test"); + const span = tracer.startSpan("test-span"); + + try { + logger.info("OTEL test endpoint called", { test: true }); + + const meter = metrics.getMeter("otel-test"); + const counter = meter.createCounter("otel_test_requests_total", { + description: "Total number of OTEL test requests", + }); + counter.add(1, { test: "true" }); + + const histogram = meter.createHistogram("otel_test_duration_seconds", { + description: "Duration of OTEL test requests", + unit: "s", + }); + + span.setAttribute("test.attribute", "test-value"); + span.setAttribute("test.timestamp", new Date().toISOString()); + + await new Promise((resolve) => setTimeout(resolve, 10)); + + const duration = (Date.now() - startTime) / 1000; + histogram.record(duration, { test: "true" }); + + recordAuthSuccess("test", "test-org"); + recordRequestEnd(startTime, 200, "/otel-test"); + recordSessionCreationDuration(startTime, true); + + span.setStatus({ code: SpanStatusCode.OK }); + span.end(); + + logger.info("OTEL test completed successfully", { + duration, + traceId: span.spanContext().traceId, + }); + + return NextResponse.json({ + status: "ok", + message: "OTEL test completed", + traceId: span.spanContext().traceId, + spanId: span.spanContext().spanId, + duration: `${duration}s`, + timestamp: new Date().toISOString(), + }); + } catch (error) { + recordAuthFailure("test", "error", "test-org"); + recordRequestEnd(startTime, 500, "/otel-test"); + recordSessionCreationDuration(startTime, false); + + logger.error("OTEL test failed", { error }); + span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) }); + span.end(); + + return NextResponse.json( + { + status: "error", + message: String(error), + traceId: span.spanContext().traceId, + }, + { status: 500 }, + ); + } +} diff --git a/apps/login/src/instrumentation.ts b/apps/login/src/instrumentation.ts new file mode 100644 index 000000000..73ab6ba93 --- /dev/null +++ b/apps/login/src/instrumentation.ts @@ -0,0 +1,87 @@ +import type { LoggerProvider } from "@opentelemetry/api-logs"; + +/** + * @module instrumentation + * + * OpenTelemetry instrumentation for Next.js applications using NodeSDK. + * Set OTEL_SDK_DISABLED=true to disable instrumentation entirely. + * + * Configuration via standard OTEL environment variables: + * + * ### Exporters (all default to "otlp") + * - OTEL_TRACES_EXPORTER: trace exporter (otlp, console, none) + * - OTEL_METRICS_EXPORTER: metric exporter (otlp, prometheus, console, none) + * - OTEL_LOGS_EXPORTER: log exporter (otlp, console, none) + * - OTEL_EXPORTER_OTLP_ENDPOINT: base endpoint for OTLP signals + * - OTEL_EXPORTER_PROMETHEUS_PORT: Prometheus scrape port (default 9464) + * + * ### Batch Processors + * - OTEL_BSP_SCHEDULE_DELAY: span export delay (default 5000ms) + * - OTEL_METRIC_EXPORT_INTERVAL: metric push interval (default 60000ms) + * + * ### Resource + * - OTEL_SERVICE_NAME: service.name attribute + * - OTEL_RESOURCE_ATTRIBUTES: additional attributes (key=value,key=value) + * + * @see https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/ + */ + +let _loggerProvider: LoggerProvider | null = null; + +export async function register(): Promise { + if (process.env.NEXT_RUNTIME !== "nodejs") return; + + const [ + { NodeSDK }, + { envDetector, processDetector, hostDetector, resourceFromAttributes }, + { containerDetector }, + { gcpDetector }, + { logs }, + { getNodeAutoInstrumentations }, + ] = await Promise.all([ + import("@opentelemetry/sdk-node"), + import("@opentelemetry/resources"), + import("@opentelemetry/resource-detector-container"), + import("@opentelemetry/resource-detector-gcp"), + import("@opentelemetry/api-logs"), + import("@opentelemetry/auto-instrumentations-node"), + ]); + + const sdk = new NodeSDK({ + resource: resourceFromAttributes({ + "deployment.environment.name": process.env.NODE_ENV || "development", + }), + resourceDetectors: [ + envDetector, + processDetector, + hostDetector, + containerDetector, + gcpDetector, + ], + instrumentations: [ + getNodeAutoInstrumentations({ + "@opentelemetry/instrumentation-http": { + ignoreIncomingRequestHook: (req) => + ["/healthy", "/_next/", "/favicon.ico", "/__nextjs", "/metrics"].some((p) => + req.url?.includes(p), + ), + }, + "@opentelemetry/instrumentation-fs": { enabled: false }, + "@opentelemetry/instrumentation-winston": { disableLogSending: false }, + }), + ], + }); + + sdk.start(); + + _loggerProvider = logs.getLoggerProvider(); + + // Use console during shutdown as the logger may be unavailable + const shutdown = () => sdk.shutdown().catch(console.error); + process.once("SIGTERM", shutdown); + process.once("SIGINT", shutdown); +} + +export function getLoggerProvider(): LoggerProvider | null { + return _loggerProvider; +} diff --git a/apps/login/src/lib/cookies.test.ts b/apps/login/src/lib/cookies.test.ts index 9205650b3..faccfce41 100644 --- a/apps/login/src/lib/cookies.test.ts +++ b/apps/login/src/lib/cookies.test.ts @@ -13,19 +13,31 @@ import { setLanguageCookie, } from "./cookies"; +import { cookies } from "next/headers"; +import { timestampDate, timestampFromMs } from "@zitadel/client"; + +// Mock logger - use vi.hoisted to ensure it's defined before vi.mock runs +const mockLogger = vi.hoisted(() => ({ + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), +})); + // Mock dependencies vi.mock("next/headers", () => ({ cookies: vi.fn(), })); +vi.mock("./logger", () => ({ + createLogger: () => mockLogger, +})); + vi.mock("@zitadel/client", () => ({ timestampDate: vi.fn((ts: any) => new Date(Number(ts.seconds) * 1000)), timestampFromMs: vi.fn((ms: number) => ({ seconds: BigInt(Math.floor(ms / 1000)) }) as any), })); -import { cookies } from "next/headers"; -import { timestampDate, timestampFromMs } from "@zitadel/client"; - describe("cookies", () => { let mockCookies: any; @@ -148,11 +160,9 @@ describe("cookies", () => { value: JSON.stringify(manySessions), }); - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); - await addSessionToCookie({ session: mockSession }); - expect(consoleSpy).toHaveBeenCalledWith("WARNING COOKIE OVERFLOW"); + expect(mockLogger.warn).toHaveBeenCalledWith("WARNING COOKIE OVERFLOW"); const setCall = mockCookies.set.mock.calls[0][0]; const sessions = JSON.parse(setCall.value); @@ -160,8 +170,6 @@ describe("cookies", () => { // Should have new session and all but the first old session expect(sessions[0]).toEqual(mockSession); expect(sessions).not.toContain(manySessions[0]); - - consoleSpy.mockRestore(); }); it("should cleanup expired sessions when cleanup is true", async () => { @@ -763,14 +771,11 @@ describe("cookies", () => { it("should return empty array when no sessions exist", async () => { mockCookies.get.mockReturnValue(undefined); - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); const result = await getAllSessions(); expect(result).toEqual([]); - expect(consoleSpy).toHaveBeenCalledWith("getAllSessions: No session cookie found, returning empty array"); - - consoleSpy.mockRestore(); + expect(mockLogger.info).toHaveBeenCalledWith("getAllSessions: No session cookie found, returning empty array"); }); }); diff --git a/apps/login/src/lib/cookies.ts b/apps/login/src/lib/cookies.ts index 29dc3c38a..2454eefc9 100644 --- a/apps/login/src/lib/cookies.ts +++ b/apps/login/src/lib/cookies.ts @@ -3,6 +3,9 @@ import { timestampDate, timestampFromMs } from "@zitadel/client"; import { cookies } from "next/headers"; import { LANGUAGE_COOKIE_NAME } from "./i18n"; +import { createLogger } from "./logger"; + +const logger = createLogger("cookies"); // TODO: improve this to handle overflow const MAX_COOKIE_SIZE = 2048; @@ -77,7 +80,7 @@ export async function addSessionToCookie({ const temp = [...currentSessions, session]; if (JSON.stringify(temp).length >= MAX_COOKIE_SIZE) { - console.log("WARNING COOKIE OVERFLOW"); + logger.warn("WARNING COOKIE OVERFLOW"); // TODO: improve cookie handling // this replaces the first session (oldest) with the new one currentSessions = [session].concat(currentSessions.slice(1)); @@ -257,7 +260,7 @@ export async function getAllSessions(cleanup: boolean = false): Promise ({ @@ -21,10 +25,6 @@ vi.mock("@zitadel/client", () => ({ create: vi.fn(), })); -import { cookies, headers } from "next/headers"; -import { userAgent } from "next/server"; -import { create } from "@zitadel/client"; - describe("fingerprint", () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/apps/login/src/lib/grpc/interceptors/otel.test.ts b/apps/login/src/lib/grpc/interceptors/otel.test.ts new file mode 100644 index 000000000..74a0811c1 --- /dev/null +++ b/apps/login/src/lib/grpc/interceptors/otel.test.ts @@ -0,0 +1,209 @@ +import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; +import { resourceFromAttributes } from "@opentelemetry/resources"; +import { + InMemorySpanExporter, + SimpleSpanProcessor, +} from "@opentelemetry/sdk-trace-base"; +import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; +import { afterAll, beforeEach, describe, expect, it } from "vitest"; +import { otelGrpcInterceptor } from "./otel"; + +const exporter = new InMemorySpanExporter(); +const provider = new NodeTracerProvider({ + resource: resourceFromAttributes({ "service.name": "test-zitadel-client" }), + spanProcessors: [new SimpleSpanProcessor(exporter)], +}); +provider.register(); + +afterAll(() => provider.shutdown()); + +describe("otelGrpcInterceptor", () => { + beforeEach(() => exporter.reset()); + + it("injects traceparent header into gRPC requests", async () => { + const capturedHeaders: Record = {}; + + await otelGrpcInterceptor(async () => ({ success: true }))({ + service: { typeName: "zitadel.session.v2.SessionService" }, + method: { name: "CreateSession" }, + url: "https://api.zitadel.example.com/zitadel.session.v2/CreateSession", + header: { + set: (key: string, value: string) => { + capturedHeaders[key.toLowerCase()] = value; + }, + }, + }); + + expect(capturedHeaders["traceparent"]).toMatch( + /^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/, + ); + }); + + it("creates client spans with RPC attributes", async () => { + await otelGrpcInterceptor(async () => ({ user: { id: "123" } }))({ + service: { typeName: "zitadel.user.v2.UserService" }, + method: { name: "GetUserByID" }, + url: "https://api.zitadel.example.com/zitadel.user.v2/GetUserByID", + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + name: "zitadel.user.v2.UserService/GetUserByID", + kind: SpanKind.CLIENT, + attributes: { + "rpc.system": "grpc", + "rpc.service": "zitadel.user.v2.UserService", + "rpc.method": "GetUserByID", + "server.address": "api.zitadel.example.com", + }, + }); + }); + + it("records error spans with gRPC status codes", async () => { + const error = Object.assign(new Error("Session not found"), { code: 5 }); + + await expect( + otelGrpcInterceptor(async () => { + throw error; + })({ + service: { typeName: "zitadel.session.v2.SessionService" }, + method: { name: "DeleteSession" }, + url: "https://api.zitadel.example.com", + header: { set: () => {} }, + }), + ).rejects.toThrow("Session not found"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + name: "zitadel.session.v2.SessionService/DeleteSession", + status: { code: SpanStatusCode.ERROR, message: "Session not found" }, + attributes: { "rpc.grpc.status_code": 5 }, + }); + expect(spans[0].events).toHaveLength(1); + expect(spans[0].events[0].name).toBe("exception"); + }); + + it("handles missing service/method gracefully", async () => { + await otelGrpcInterceptor(async () => ({ ok: true }))({ + url: "https://api.zitadel.example.com", + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + name: "unknown/unknown", + attributes: { "rpc.service": "unknown", "rpc.method": "unknown" }, + }); + }); + + it("returns the response from the next handler", async () => { + const expectedResponse = { data: "test-data", items: [1, 2, 3] }; + + const result = await otelGrpcInterceptor(async () => expectedResponse)({ + service: { typeName: "TestService" }, + method: { name: "TestMethod" }, + url: "https://test.example.com", + header: { set: () => {} }, + }); + + expect(result).toEqual(expectedResponse); + }); + + it("records error spans without gRPC status code", async () => { + const error = new Error("Network failure"); + + await expect( + otelGrpcInterceptor(async () => { + throw error; + })({ + service: { typeName: "zitadel.user.v2.UserService" }, + method: { name: "ListUsers" }, + url: "https://api.zitadel.example.com", + header: { set: () => {} }, + }), + ).rejects.toThrow("Network failure"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + name: "zitadel.user.v2.UserService/ListUsers", + status: { code: SpanStatusCode.ERROR, message: "Network failure" }, + }); + expect(spans[0].attributes).not.toHaveProperty("rpc.grpc.status_code"); + }); + + it("handles malformed URL gracefully", async () => { + await otelGrpcInterceptor(async () => ({ ok: true }))({ + service: { typeName: "TestService" }, + method: { name: "TestMethod" }, + url: "not-a-valid-url", + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0].attributes["server.address"]).toBe("unknown"); + }); + + it("handles missing URL gracefully", async () => { + await otelGrpcInterceptor(async () => ({ ok: true }))({ + service: { typeName: "TestService" }, + method: { name: "TestMethod" }, + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0].attributes["server.address"]).toBe("unknown"); + }); + + it("handles empty string URL gracefully", async () => { + await otelGrpcInterceptor(async () => ({ ok: true }))({ + service: { typeName: "TestService" }, + method: { name: "TestMethod" }, + url: "", + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0].attributes["server.address"]).toBe("unknown"); + }); + + it("uses empty strings as-is for service/method names", async () => { + await otelGrpcInterceptor(async () => ({ ok: true }))({ + service: { typeName: "" }, + method: { name: "" }, + url: "https://api.example.com", + header: { set: () => {} }, + }); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + name: "/", + attributes: { "rpc.service": "", "rpc.method": "" }, + }); + }); + + it("handles non-Error exceptions", async () => { + await expect( + otelGrpcInterceptor(async () => { + throw "string error"; + })({ + service: { typeName: "TestService" }, + method: { name: "TestMethod" }, + url: "https://api.example.com", + header: { set: () => {} }, + }), + ).rejects.toBe("string error"); + + const spans = exporter.getFinishedSpans(); + expect(spans).toHaveLength(1); + expect(spans[0].status.code).toBe(SpanStatusCode.ERROR); + }); +}); diff --git a/apps/login/src/lib/grpc/interceptors/otel.ts b/apps/login/src/lib/grpc/interceptors/otel.ts new file mode 100644 index 000000000..8a03ed834 --- /dev/null +++ b/apps/login/src/lib/grpc/interceptors/otel.ts @@ -0,0 +1,98 @@ +/** + * OpenTelemetry gRPC/Connect Interceptor + * + * Provides distributed tracing for Connect/gRPC calls. Creates client spans + * with RPC semantic conventions, propagates W3C trace context headers + * (traceparent/tracestate) and baggage, and records errors with gRPC status + * codes. + * + * @see https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ + * + * @example + * ```typescript + * import { otelGrpcInterceptor } from "@/lib/grpc/interceptors/otel"; + * + * const transport = createTransport({ + * baseUrl: "https://api.example.com", + * interceptors: [otelGrpcInterceptor], + * }); + * ``` + */ + +import { + context, + propagation, + SpanKind, + SpanStatusCode, + trace, +} from "@opentelemetry/api"; +import { GrpcError, Interceptor } from "./types"; + +const TRACER_NAME = "zitadel-login-grpc" as const; + +/** + * Extracts the hostname from a URL string. Returns "unknown" if the URL is + * undefined or cannot be parsed. + */ +function parseHostname(url: string | undefined): string { + if (!url) { + return "unknown"; + } + try { + return new URL(url).hostname; + } catch { + return "unknown"; + } +} + +/** + * OpenTelemetry interceptor for Connect/gRPC calls. Wraps each RPC call in a + * client span, injects trace context headers for distributed tracing, and + * records any errors that occur during the call. + */ +export function otelGrpcInterceptor(next: Parameters[0]): ReturnType { + return async function tracedCall(req) { + const serviceName: string = req.service?.typeName ?? "unknown"; + const methodName: string = req.method?.name ?? "unknown"; + + return trace.getTracer(TRACER_NAME).startActiveSpan( + `${serviceName}/${methodName}`, + { + kind: SpanKind.CLIENT, + attributes: { + "rpc.system": "grpc", + "rpc.service": serviceName, + "rpc.method": methodName, + "server.address": parseHostname(req.url), + }, + }, + async (span) => { + const ctx = trace.setSpan(context.active(), span); + propagation.inject(ctx, req.header, { + set: (carrier, key, value) => carrier.set(key, value), + }); + + try { + const response = await next(req); + span.setStatus({ code: SpanStatusCode.OK }); + return response; + } catch (err: unknown) { + const exception = + err instanceof Error ? err : new Error(String(err)); + span.recordException(exception); + const grpcError = err as GrpcError; + if (grpcError.code !== undefined) { + span.setAttribute("rpc.grpc.status_code", grpcError.code); + } + span.setStatus({ + code: SpanStatusCode.ERROR, + message: exception.message, + }); + throw err; + } finally { + span.end(); + } + }, + ); + }; +} diff --git a/apps/login/src/lib/grpc/interceptors/types.ts b/apps/login/src/lib/grpc/interceptors/types.ts new file mode 100644 index 000000000..396906604 --- /dev/null +++ b/apps/login/src/lib/grpc/interceptors/types.ts @@ -0,0 +1,18 @@ +export interface GrpcRequestHeader { + readonly set: (key: string, value: string) => void; +} + +export interface GrpcRequest { + readonly service?: { readonly typeName: string }; + readonly method?: { readonly name: string }; + readonly url?: string; + readonly header: GrpcRequestHeader; +} + +export interface GrpcError extends Error { + readonly code?: number; +} + +export type Interceptor = ( + next: (req: GrpcRequest) => Promise, +) => (req: GrpcRequest) => Promise; diff --git a/apps/login/src/lib/logger.ts b/apps/login/src/lib/logger.ts new file mode 100644 index 000000000..aedb4c824 --- /dev/null +++ b/apps/login/src/lib/logger.ts @@ -0,0 +1,71 @@ +import "server-only"; +import winston from "winston"; + +const isProduction = process.env.NODE_ENV === "production"; + +/** + * Winston logger configuration for the login application. + * + * OpenTelemetry integration: + * - @opentelemetry/instrumentation-winston automatically injects trace context + * (trace_id, span_id, trace_flags) into every log entry + * - @opentelemetry/winston-transport automatically sends logs to OTEL LoggerProvider + * when the SDK is initialized with a LoggerProvider + * + * The trace context injection and OTEL log sending are handled by the instrumentation + * registered in instrumentation.ts - no manual setup required here. + */ + +// Custom format for development - colorized and readable +const devFormat = winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + winston.format.printf(({ level, message, timestamp, ...meta }) => { + const metaStr = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ""; + return `${timestamp} ${level}: ${message}${metaStr}`; + }), +); + +// Production format - JSON for structured logging +const prodFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json(), +); + +/** + * The main logger instance. + * In production, outputs JSON for structured logging. + * In development, outputs colorized human-readable format. + */ +export const logger = winston.createLogger({ + level: process.env.OTEL_LOG_LEVEL || process.env.LOG_LEVEL || "info", + format: isProduction ? prodFormat : devFormat, + defaultMeta: { + service: process.env.OTEL_SERVICE_NAME || "zitadel-login", + }, + transports: [ + new winston.transports.Console({ + // In production, use stderr for error level, stdout for others + stderrLevels: isProduction ? ["error"] : [], + }), + ], +}); + +/** + * Create a child logger with a specific context. + * The context is added as metadata to all log entries from this logger. + * + * @param context - The context name for this logger (e.g., 'auth-flow', 'session') + * @param metadata - Optional additional metadata to include in all log messages + * @returns A child logger instance + */ +export function createLogger( + context: string, + metadata?: Record, +): winston.Logger { + return logger.child({ context, ...metadata }); +} + +// Re-export winston types for convenience +export type { Logger } from "winston"; diff --git a/apps/login/src/lib/metrics.ts b/apps/login/src/lib/metrics.ts new file mode 100644 index 000000000..8b39f1c78 --- /dev/null +++ b/apps/login/src/lib/metrics.ts @@ -0,0 +1,146 @@ +import "server-only"; +import { metrics } from "@opentelemetry/api"; + +const serviceName = process.env.OTEL_SERVICE_NAME || "zitadel-login"; +const meter = metrics.getMeter(serviceName); + +const requestCounter = meter.createCounter("http_server_requests_total", { + description: "Total number of HTTP requests", +}); + +const requestDuration = meter.createHistogram( + "http_server_request_duration_seconds", + { + description: "HTTP request duration in seconds", + unit: "s", + }, +); + +const requestSize = meter.createHistogram("http_server_request_size_bytes", { + description: "Size of HTTP request bodies in bytes", + unit: "By", +}); + +const responseSize = meter.createHistogram("http_server_response_size_bytes", { + description: "Size of HTTP response bodies in bytes", + unit: "By", +}); + +const responseStatusCounter = meter.createCounter( + "http_server_response_status_total", + { + description: "Count of HTTP responses per status code", + }, +); + +const activeRequests = meter.createUpDownCounter("http_server_active_requests", { + description: "Number of active HTTP requests", +}); + +const authAttempts = meter.createCounter("login_auth_attempts_total", { + description: "Total authentication attempts", +}); + +const authSuccesses = meter.createCounter("login_auth_successes_total", { + description: "Total successful authentications", +}); + +const authFailures = meter.createCounter("login_auth_failures_total", { + description: "Total failed authentications", +}); + +const sessionCreationDuration = meter.createHistogram( + "login_session_creation_duration_seconds", + { + description: "Session creation duration in seconds", + unit: "s", + }, +); + +/** + * Record an authentication attempt. + */ +export function recordAuthAttempt(method: string, organization?: string): void { + authAttempts.add(1, { + method, + organization: organization || "unknown", + }); +} + +/** + * Record a successful authentication. + */ +export function recordAuthSuccess(method: string, organization?: string): void { + authSuccesses.add(1, { + method, + organization: organization || "unknown", + }); +} + +/** + * Record a failed authentication. + */ +export function recordAuthFailure( + method: string, + reason: string, + organization?: string, +): void { + authFailures.add(1, { + method, + reason, + organization: organization || "unknown", + }); +} + +/** + * Record the start of an HTTP request. + */ +export function recordRequestStart(): void { + activeRequests.add(1); + requestCounter.add(1); +} + +/** + * Record the end of an HTTP request. + */ +export function recordRequestEnd( + startTime: number, + statusCode: number, + route: string, + requestBytes?: number, + responseBytes?: number, +): void { + activeRequests.add(-1); + + const labels = { + status_code: statusCode.toString(), + route, + }; + + const duration = (Date.now() - startTime) / 1000; + requestDuration.record(duration, labels); + + responseStatusCounter.add(1, { + status_code: statusCode.toString(), + }); + + if (requestBytes !== undefined) { + requestSize.record(requestBytes, labels); + } + if (responseBytes !== undefined) { + responseSize.record(responseBytes, labels); + } +} + +/** + * Record session creation duration. + */ +export function recordSessionCreationDuration( + startTime: number, + success: boolean, +): void { + const duration = (Date.now() - startTime) / 1000; + sessionCreationDuration.record(duration, { + success: success.toString(), + }); +} diff --git a/apps/login/src/lib/server/auth-flow.ts b/apps/login/src/lib/server/auth-flow.ts index 6fc0681ba..4c5823d7b 100644 --- a/apps/login/src/lib/server/auth-flow.ts +++ b/apps/login/src/lib/server/auth-flow.ts @@ -1,6 +1,7 @@ "use server"; import { getAllSessions } from "@/lib/cookies"; +import { createLogger } from "@/lib/logger"; import { loginWithOIDCAndSession } from "@/lib/oidc"; import { loginWithSAMLAndSession } from "@/lib/saml"; import { getServiceConfig } from "@/lib/service-url"; @@ -8,6 +9,8 @@ import { listSessions, ServiceConfig } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { headers } from "next/headers"; +const logger = createLogger("auth-flow"); + export interface AuthFlowParams { sessionId: string; requestId: string; @@ -54,7 +57,7 @@ export async function completeAuthFlow( // Safety net - ensure we always return a valid object if (!result || typeof result !== "object" || (!("redirect" in result) && !("error" in result))) { - console.error("Auth flow: Invalid result from loginWithOIDCAndSession:", result); + logger.error("Auth flow: Invalid result from loginWithOIDCAndSession:", { result }); return { error: "Authentication completed but navigation failed" }; } @@ -75,7 +78,7 @@ export async function completeAuthFlow( typeof result !== "object" || (!("redirect" in result) && !("error" in result) && !("samlData" in result)) ) { - console.error("Auth flow: Invalid result from loginWithSAMLAndSession:", result); + logger.error("Auth flow: Invalid result from loginWithSAMLAndSession:", { result }); return { error: "Authentication completed but navigation failed" }; } diff --git a/apps/login/src/lib/server/cookie.ts b/apps/login/src/lib/server/cookie.ts index 069969810..edfbc9bbe 100644 --- a/apps/login/src/lib/server/cookie.ts +++ b/apps/login/src/lib/server/cookie.ts @@ -1,6 +1,7 @@ "use server"; import { addSessionToCookie, updateSessionCookie } from "@/lib/cookies"; +import { createLogger } from "@/lib/logger"; import { createSessionForUserIdAndIdpIntent, createSessionFromChecksAndChallenges, @@ -16,6 +17,8 @@ import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; import { getServiceConfig } from "../service-url"; +const logger = createLogger("cookie"); + type CustomCookieData = { id: string; token: string; @@ -52,7 +55,7 @@ export async function createSessionAndUpdateCookie(command: { let sessionLifetime = command.lifetime; if (!sessionLifetime || !sessionLifetime.seconds) { - console.warn("No session lifetime provided, using default of 24 hours."); + logger.warn("No session lifetime provided, using default of 24 hours"); sessionLifetime = { seconds: BigInt(24 * 60 * 60), // 24 hours @@ -126,7 +129,7 @@ export async function createSessionForIdpAndUpdateCookie({ let sessionLifetime = lifetime; if (!sessionLifetime || !sessionLifetime.seconds) { - console.warn("No IDP session lifetime provided, using default of 24 hours."); + logger.warn("No IDP session lifetime provided, using default of 24 hours"); sessionLifetime = { seconds: BigInt(24 * 60 * 60), // 24 hours @@ -140,7 +143,7 @@ export async function createSessionForIdpAndUpdateCookie({ idpIntent, lifetime: sessionLifetime, }).catch((error: ErrorDetail | CredentialsCheckError) => { - console.error("Could not set session", error); + logger.error("Could not set session", { error }); if ("failedAttempts" in error && error.failedAttempts) { throw { error: `Failed to authenticate: You had ${error.failedAttempts} password attempts.`, diff --git a/apps/login/src/lib/server/flow-initiation.ts b/apps/login/src/lib/server/flow-initiation.ts index cee680a2a..848d13572 100644 --- a/apps/login/src/lib/server/flow-initiation.ts +++ b/apps/login/src/lib/server/flow-initiation.ts @@ -1,4 +1,5 @@ import { idpTypeToSlug } from "@/lib/idp"; +import { createLogger } from "@/lib/logger"; import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname"; import { constructUrl } from "@/lib/service-url"; import { findValidSession } from "@/lib/session"; @@ -24,6 +25,8 @@ import { NextRequest, NextResponse } from "next/server"; import { buildCSP } from "../csp"; import escapeHtml from "escape-html"; +const logger = createLogger("flow-initiation"); + const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]+)/; const ORG_DOMAIN_SCOPE_REGEX = /urn:zitadel:iam:org:domain:primary:(.+)/; const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; @@ -103,7 +106,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope); const orgDomain = matched?.[1] ?? ""; - console.log("Extracted Organization Domain:", orgDomain); + logger.info("Extracted org domain:", { orgDomain }); if (orgDomain) { const orgs = await getOrgsByDomain({ serviceConfig, domain: orgDomain }); @@ -238,7 +241,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr return NextResponse.redirect(absoluteUrl.toString()); } } catch (error) { - console.error("Failed to execute sendLoginname:", error); + logger.error("Failed to execute sendLoginname:", { error }); } } @@ -333,7 +336,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr if (callbackUrl) { return NextResponse.redirect(callbackUrl); } else { - console.log("could not create callback, redirect user to choose other account"); + logger.info("could not create callback, redirect user to choose other account"); return gotoAccounts({ request, organization, @@ -341,7 +344,7 @@ export async function handleOIDCFlowInitiation(params: FlowInitiationParams): Pr }); } } catch (error) { - console.error(error); + logger.error("Error creating callback:", { error }); return gotoAccounts({ request, requestId, @@ -451,7 +454,7 @@ export async function handleSAMLFlowInitiation(params: FlowInitiationParams): Pr }); } } catch (error) { - console.error("SAML createResponse failed:", error); + logger.error("SAML createResponse failed:", { error }); } // Final fallback: SAML response creation failed - show account selection diff --git a/apps/login/src/lib/server/idp-intent.ts b/apps/login/src/lib/server/idp-intent.ts index 8fc396515..b17db302d 100644 --- a/apps/login/src/lib/server/idp-intent.ts +++ b/apps/login/src/lib/server/idp-intent.ts @@ -1,6 +1,7 @@ "use server"; import { getSessionCookieById } from "@/lib/cookies"; +import { createLogger } from "@/lib/logger"; import { getServiceConfig } from "@/lib/service-url"; import { addHuman, @@ -31,6 +32,8 @@ import { headers } from "next/headers"; import { getFingerprintIdCookie } from "../fingerprint"; import { createNewSessionFromIdpIntent } from "./idp"; +const logger = createLogger("idp-intent"); + const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; async function resolveOrganizationForUser({ @@ -139,7 +142,7 @@ async function resolveUserIdFromSession({ try { const sessionCookie = await getSessionCookieById({ sessionId }); if (!sessionCookie) { - console.warn("[IDP Process] Session for linking not found or invalid"); + logger.warn("Session for linking not found or invalid"); return { redirect: `/idp/${provider}/linking-failed?error=session_invalid` }; } @@ -151,13 +154,13 @@ async function resolveUserIdFromSession({ const session = sessionResp.session; if (!session?.factors?.user?.id) { - console.warn("[IDP Process] Session found but no userId associated for linking."); + logger.warn("Session found but no userId associated for linking"); return { redirect: `/idp/${provider}/linking-failed?error=session_invalid` }; } return { userId: session.factors.user.id }; } catch (error) { - console.warn("[IDP Process] Error retrieving session for linking:", error); + logger.warn("Error retrieving session for linking", { error }); return { redirect: `/idp/${provider}/linking-failed?error=session_invalid` }; } } @@ -173,7 +176,7 @@ async function handleExplicitLinking(ctx: IDPHandlerContext): Promise { const { buildRedirectParams } = ctx; - console.log("[IDP Process] No matching user and creation not allowed"); + logger.debug("No matching user and creation not allowed"); const params = buildRedirectParams(); return { redirect: `/idp/${ctx.params.provider}/account-not-found?${params}` }; } @@ -604,7 +607,7 @@ export async function processIDPCallback({ // Validate required parameters if (!provider || !id || !token) { - console.error("[IDP Process] Missing required parameters:", { provider, id, hasToken: !!token }); + logger.error("Missing required parameters", { provider, id, hasToken: !!token }); const errorParams = new URLSearchParams(); if (requestId) errorParams.set("requestId", requestId); if (organization) errorParams.set("organization", organization); @@ -617,13 +620,13 @@ export async function processIDPCallback({ // Consume the single-use token ONCE const intent = await retrieveIDPIntent({ serviceConfig, id, token }); - console.log("[IDP Process] Intent retrieved successfully, processing business logic"); + logger.debug("Intent retrieved successfully, processing business logic"); const { idpInformation } = intent; // Verify we have IDP info early on if (!idpInformation) { - console.error("[IDP Process] IDP information missing"); + logger.error("IDP information missing"); return { redirect: `/idp/${provider}/failure?error=missing_idp_info` }; } @@ -693,7 +696,7 @@ export async function processIDPCallback({ // Should theoretically be unreachable if handleNoUserFound covers the rest return { error: t("errors.unknown") }; } catch (error: unknown) { - console.error("[IDP Process] Error processing intent:", error); + logger.error("Error processing intent", { error }); const errorParams = new URLSearchParams(); if (requestId) errorParams.set("requestId", requestId); diff --git a/apps/login/src/lib/server/loginname.ts b/apps/login/src/lib/server/loginname.ts index 1d2b9f085..2802e4a35 100644 --- a/apps/login/src/lib/server/loginname.ts +++ b/apps/login/src/lib/server/loginname.ts @@ -1,5 +1,6 @@ "use server"; +import { createLogger } from "@/lib/logger"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; @@ -25,6 +26,8 @@ import { import { createSessionAndUpdateCookie } from "./cookie"; import { getPublicHost } from "./host"; +const logger = createLogger("loginname"); + export type SendLoginnameCommand = { loginName: string; requestId?: string; @@ -60,17 +63,17 @@ export async function sendLoginname(command: SendLoginnameCommand) { // Safety check: ensure searchResult is defined if (!searchResult) { - console.error("searchUsers returned undefined or null"); + logger.error("searchUsers returned undefined or null"); return { error: t("errors.couldNotSearchUsers") }; } if ("error" in searchResult && searchResult.error) { - console.log("searchUsers returned error, returning early:", searchResult.error); + logger.debug("searchUsers returned error, returning early", { error: searchResult.error }); return searchResult; } if (!("result" in searchResult)) { - console.log("searchUsers has no result field"); + logger.debug("searchUsers has no result field"); return { error: t("errors.couldNotSearchUsers") }; } @@ -80,12 +83,12 @@ export async function sendLoginname(command: SendLoginnameCommand) { const users = potentialUsers ?? []; if (users.length === 0) { - console.log("No users found, will proceed with org discovery"); + logger.debug("No users found, will proceed with org discovery"); } const preventUserEnumeration = (organization: string | undefined) => { if (command.ignoreUnknownUsernames) { - console.log("ignoreUnknownUsernames is true, redirecting to password"); + logger.debug("ignoreUnknownUsernames is true, redirecting to password"); const paramsPasswordDefault = new URLSearchParams({ loginName: command.loginName, }); @@ -229,7 +232,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { }; if (users.length > 1) { - console.log("multiple users found, returning error"); + logger.debug("multiple users found, returning error"); if (loginSettingsByContext?.ignoreUnknownUsernames) { return preventUserEnumeration(command.organization); } @@ -331,7 +334,9 @@ export async function sendLoginname(command: SendLoginnameCommand) { // always resend invite or setup email if user has no primary auth method set if (!hasPrimaryMethod) { - console.log("humanUser.email?.isVerified", humanUser?.email?.isVerified); + logger.debug("humanUser.email?.isVerified", { + isVerified: humanUser?.email?.isVerified, + }); const params = new URLSearchParams({ loginName: (session?.factors?.user?.loginName ?? user.preferredLoginName) as string, send: "true", // set this to true to request a new code immediately @@ -482,7 +487,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } } - console.log("user not found (0 potential users), checking registration options"); + logger.debug("User not found (0 potential users), checking registration options"); // user not found, perform organization discovery if no org context provided let discoveredOrganization = command.organization; @@ -508,33 +513,33 @@ export async function sendLoginname(command: SendLoginnameCommand) { const orgLoginSettings = await getLoginSettings({ serviceConfig, organization: orgToCheckForDiscovery }); if (orgLoginSettings?.allowDomainDiscovery) { - console.log("org discovery successful, using org:", orgToCheckForDiscovery); + logger.debug("Org discovery successful", { organization: orgToCheckForDiscovery }); discoveredOrganization = orgToCheckForDiscovery; // Use the discovered organization's login settings for subsequent checks effectiveLoginSettings = orgLoginSettings; } else { - console.log("org does not allow domain discovery"); + logger.debug("Org does not allow domain discovery"); } } else { - console.log("no single org found for discovery"); + logger.debug("No single org found for discovery"); } } // user not found, check if register is enabled on instance / organization context if (effectiveLoginSettings?.allowRegister && !effectiveLoginSettings?.allowLocalAuthentication) { - console.log("redirecting to IDP (register allowed, password not allowed)"); + logger.debug("redirecting to IDP (register allowed, password not allowed)"); const resp = await redirectUserToIDP(undefined, discoveredOrganization); if (resp) { return resp; } - console.log("IDP redirect failed, returning user not found"); + logger.debug("IDP redirect failed, returning user not found"); return preventUserEnumeration(discoveredOrganization); } else if (effectiveLoginSettings?.allowRegister && effectiveLoginSettings?.allowLocalAuthentication) { - console.log("register and password both allowed"); + logger.debug("register and password both allowed"); // do not register user if ignoreUnknownUsernames is set if (discoveredOrganization && !effectiveLoginSettings?.ignoreUnknownUsernames) { - console.log("redirecting to registration page with org:", discoveredOrganization); + logger.debug("Redirecting to registration page", { organization: discoveredOrganization }); const params = new URLSearchParams({ organization: discoveredOrganization }); if (command.requestId) { @@ -547,7 +552,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { return { redirect: "/register?" + params }; } else { - console.log("not redirecting to register:", { + logger.debug("Not redirecting to register", { hasDiscoveredOrg: !!discoveredOrganization, ignoreUnknownUsernames: effectiveLoginSettings?.ignoreUnknownUsernames, }); diff --git a/apps/login/src/lib/server/passkeys.ts b/apps/login/src/lib/server/passkeys.ts index c3349127c..e948681d0 100644 --- a/apps/login/src/lib/server/passkeys.ts +++ b/apps/login/src/lib/server/passkeys.ts @@ -1,5 +1,6 @@ "use server"; +import { createLogger } from "@/lib/logger"; import { createPasskeyRegistrationLink, getLoginSettings, @@ -27,6 +28,8 @@ import { updateOrCreateSession } from "./session"; import { completeFlowOrGetUrl } from "../client"; import { createSessionAndUpdateCookie } from "./cookie"; +const logger = createLogger("passkeys"); + type VerifyPasskeyCommand = { passkeyId: string; passkeyName?: string; @@ -103,7 +106,7 @@ export async function registerPasskeyLink( // check if a verification was done earlier const hasValidUserVerificationCheck = await checkUserVerification(currentUserId); - console.log("hasValidUserVerificationCheck", hasValidUserVerificationCheck); + logger.info("hasValidUserVerificationCheck", { hasValidUserVerificationCheck }); if (!hasValidUserVerificationCheck) { return { error: "User Verification Check has to be done" }; } @@ -295,7 +298,7 @@ export async function sendPasskey(command: SendPasskeyCommand) { try { userResponse = await getUserByID({ serviceConfig, userId }); } catch (error) { - console.error("Error fetching user by ID:", error); + logger.error("Error fetching user by ID:", { error }); return { error: t("verify.errors.couldNotGetUser") }; } diff --git a/apps/login/src/lib/server/password.ts b/apps/login/src/lib/server/password.ts index 592ecfc1a..a1369b043 100644 --- a/apps/login/src/lib/server/password.ts +++ b/apps/login/src/lib/server/password.ts @@ -1,5 +1,7 @@ "use server"; +import { createLogger } from "@/lib/logger"; +import { recordAuthAttempt, recordAuthSuccess, recordAuthFailure } from "@/lib/metrics"; import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "@/lib/server/cookie"; import { getLockoutSettings, @@ -31,6 +33,8 @@ import { } from "../verify-helper"; import { getPublicHostWithProtocol } from "./host"; +const logger = createLogger("password"); + type ResetPasswordCommand = { loginName: string; organization?: string; @@ -134,6 +138,8 @@ export async function sendPassword( const { serviceConfig } = getServiceConfig(_headers); const t = await getTranslations("password"); + recordAuthAttempt("password", command.organization); + let sessionCookie = await getSessionCookieByLoginName({ loginName: command.loginName, organization: command.organization, @@ -152,7 +158,7 @@ export async function sendPassword( let lifetime = loginSettingsByUser.passwordCheckLifetime; if (!lifetime || !lifetime.seconds) { - console.warn("No password lifetime provided, defaulting to 24 hours"); + logger.warn("No password lifetime provided, defaulting to 24 hours"); lifetime = { seconds: BigInt(60 * 60 * 24), // default to 24 hours nanos: 0, @@ -170,7 +176,7 @@ export async function sendPassword( throw new Error("Could not load login settings"); } } catch { - console.warn("[Password] Could not update session"); + logger.warn("[Password] Could not update session"); // If the session was terminated or any other error occurred during update, // we fall back to creating a new session. sessionCookie = undefined; @@ -189,6 +195,7 @@ export async function sendPassword( // Force fallback if settings can't be loaded if (!loginSettingsByContext) { // this is a fake error message to hide that the user does not even exist + recordAuthFailure("password", "settings_unavailable", command.organization); return { error: t("errors.couldNotVerifyPassword") }; } @@ -215,6 +222,7 @@ export async function sendPassword( if (userLoginSettings?.disableLoginWithEmail && userLoginSettings?.disableLoginWithPhone) { if (user.preferredLoginName !== command.loginName) { // emulate user not found to prevent enumeration (use context settings not user settings) + recordAuthFailure("password", "login_name_mismatch", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -222,6 +230,7 @@ export async function sendPassword( } } else if (userLoginSettings?.disableLoginWithEmail) { if (user.preferredLoginName !== command.loginName && humanUser?.phone?.phone !== command.loginName) { + recordAuthFailure("password", "login_name_mismatch", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -229,6 +238,7 @@ export async function sendPassword( } } else if (userLoginSettings?.disableLoginWithPhone) { if (user.preferredLoginName !== command.loginName && humanUser?.email?.email !== command.loginName) { + recordAuthFailure("password", "login_name_mismatch", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -251,6 +261,7 @@ export async function sendPassword( sessionCookie = result.sessionCookie; } catch (error: any) { if ("failedAttempts" in error && error.failedAttempts) { + recordAuthFailure("password", "invalid_password", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -269,6 +280,7 @@ export async function sendPassword( }), }; } + recordAuthFailure("password", "session_creation_failed", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -276,6 +288,7 @@ export async function sendPassword( } } else { // this is a fake error message to hide that the user does not even exist + recordAuthFailure("password", "user_not_found", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -284,6 +297,7 @@ export async function sendPassword( } if (!session?.factors?.user?.id) { + recordAuthFailure("password", "session_invalid", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -293,12 +307,14 @@ export async function sendPassword( if (!user) { const userResponse = await getUserByID({ serviceConfig, userId: session?.factors?.user?.id }); if (!userResponse.user) { + recordAuthFailure("password", "user_not_found", command.organization); return { error: t("errors.userNotFound") }; } user = userResponse.user; } if (!session?.factors?.user?.id || !sessionCookie) { + recordAuthFailure("password", "session_invalid", command.organization); if (loginSettingsByContext?.ignoreUnknownUsernames) { return { error: t("errors.failedToAuthenticateNoLimit") }; } @@ -334,6 +350,7 @@ export async function sendPassword( // throw error if user is in initial state here and do not continue if (user.state === UserState.INITIAL) { + recordAuthFailure("password", "user_initial_state", command.organization); return { error: t("errors.initialUserNotSupported") }; } @@ -354,6 +371,7 @@ export async function sendPassword( } if (!authMethods) { + recordAuthFailure("password", "no_auth_methods", command.organization); return { error: t("errors.couldNotVerifyPassword") }; } @@ -371,10 +389,10 @@ export async function sendPassword( } let result: Awaited>; - + if (command.requestId && session.id) { // OIDC/SAML flow - console.log("Password auth: OIDC/SAML flow with requestId:", command.requestId, "sessionId:", session.id); + logger.info("Password auth: OIDC/SAML flow with requestId:", { requestId: command.requestId, sessionId: session.id }); result = await completeFlowOrGetUrl( { sessionId: session.id, @@ -385,7 +403,7 @@ export async function sendPassword( ); } else { // Regular flow (no requestId) - console.log("Password auth: Regular flow with loginName:", session.factors.user.loginName); + logger.info("Password auth: Regular flow with loginName:", { loginName: session.factors.user.loginName }); result = await completeFlowOrGetUrl( { loginName: session.factors.user.loginName, @@ -396,9 +414,15 @@ export async function sendPassword( } if (result && typeof result === "object") { + if ("redirect" in result) { + recordAuthSuccess("password", command.organization); + } else if ("error" in result) { + recordAuthFailure("password", "flow_error", command.organization); + } return result; } + recordAuthFailure("password", "navigation_failed", command.organization); return { error: "Authentication completed but navigation failed" }; } @@ -476,7 +500,7 @@ export async function checkSessionAndSetPassword({ }); session = sessionResponse.session; } catch (error) { - console.error("Error getting session:", error); + logger.error("Error getting session:", { error }); return { error: "Could not load session" }; } diff --git a/apps/login/src/lib/server/session.ts b/apps/login/src/lib/server/session.ts index ca27cc2ec..fff38e227 100644 --- a/apps/login/src/lib/server/session.ts +++ b/apps/login/src/lib/server/session.ts @@ -1,5 +1,6 @@ "use server"; +import { createLogger } from "@/lib/logger"; import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "@/lib/server/cookie"; import { deleteSession, @@ -25,6 +26,8 @@ import { import { getServiceConfig } from "../service-url"; import { getPublicHost } from "./host"; +const logger = createLogger("session"); + export async function skipMFAAndContinueWithNextUrl({ userId, requestId, @@ -149,7 +152,7 @@ export async function updateOrCreateSession(options: UpdateSessionCommand) { challenges, requestId, }).catch((error) => { - console.error("Could not create session", error); + logger.error("Could not create session", { error }); return undefined; }); @@ -175,7 +178,7 @@ export async function updateOrCreateSession(options: UpdateSessionCommand) { } if (!lifetime || !lifetime.seconds) { - console.warn("No lifetime provided for session, defaulting to 24 hours"); + logger.warn("No lifetime provided for session, defaulting to 24 hours"); lifetime = { seconds: BigInt(60 * 60 * 24), // default to 24 hours nanos: 0, diff --git a/apps/login/src/lib/server/verify.test.ts b/apps/login/src/lib/server/verify.test.ts index ecf90b1db..d4d92835a 100644 --- a/apps/login/src/lib/server/verify.test.ts +++ b/apps/login/src/lib/server/verify.test.ts @@ -1,6 +1,11 @@ import { vi, describe, expect, test, beforeEach } from "vitest"; import { sendVerification } from "./verify"; +import { verifyEmail, getUserByID, getSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; +import { getSessionCookieByLoginName } from "../cookies"; +import { createSessionAndUpdateCookie } from "./cookie"; +import { cookies } from "next/headers"; + // Mock dependencies vi.mock("@/lib/zitadel", () => ({ verifyEmail: vi.fn(), @@ -36,11 +41,6 @@ vi.mock("next-intl/server", () => ({ getTranslations: vi.fn(() => (key: string) => key), })); -import { verifyEmail, getUserByID, getSession, listAuthenticationMethodTypes } from "@/lib/zitadel"; -import { getSessionCookieByLoginName } from "../cookies"; -import { createSessionAndUpdateCookie } from "./cookie"; -import { cookies } from "next/headers"; - describe("sendVerification", () => { let mockVerifyEmail: any; let mockGetUserByID: any; diff --git a/apps/login/src/lib/server/verify.ts b/apps/login/src/lib/server/verify.ts index f687811c7..c48fe3561 100644 --- a/apps/login/src/lib/server/verify.ts +++ b/apps/login/src/lib/server/verify.ts @@ -1,5 +1,6 @@ "use server"; +import { createLogger } from "@/lib/logger"; import { createInviteCode, getLoginSettings, @@ -28,6 +29,8 @@ import { createSessionAndUpdateCookie } from "./cookie"; import { getPublicHostWithProtocol } from "./host"; import { getTranslations } from "next-intl/server"; +const logger = createLogger("verify"); + export async function verifyTOTP(code: string, loginName?: string, organization?: string) { const _headers = await headers(); const { serviceConfig } = getServiceConfig(_headers); @@ -63,11 +66,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const verifyResponse = command.isInvite ? await verifyInviteCode({ serviceConfig, userId: command.userId, verificationCode: command.code }).catch((error) => { - console.warn(error); + logger.warn("Could not verify invite:", { error }); return { error: t("errors.couldNotVerifyInvite") }; }) : await verifyEmail({ serviceConfig, userId: command.userId, verificationCode: command.code }).catch((error) => { - console.warn(error); + logger.warn("Could not verify email:", { error }); return { error: t("errors.couldNotVerifyEmail") }; }); @@ -102,7 +105,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { }) .catch((error) => { // user session is not found, so we create a new one - console.warn("[verify] user session is not found, so we create a new one", error); + logger.warn("[verify] user session is not found, so we create a new one", { error }); return undefined; }); } diff --git a/apps/login/src/lib/verify-helper.test.ts b/apps/login/src/lib/verify-helper.test.ts index 47357755d..2b3890a91 100644 --- a/apps/login/src/lib/verify-helper.test.ts +++ b/apps/login/src/lib/verify-helper.test.ts @@ -4,6 +4,15 @@ import { cookies } from "next/headers"; import { getFingerprintIdCookie } from "./fingerprint"; import crypto from "crypto"; +import { + checkPasswordChangeRequired, + checkEmailVerified, + checkEmailVerification, + checkMFAFactors, + checkUserVerification, +} from "./verify-helper"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; + // Mock function to create timestamps - following the same pattern as session.test.ts function createMockTimestamp(offsetMs = 3600000): any { return { @@ -324,15 +333,6 @@ describe("shouldEnforceMFA", () => { }); }); -import { - checkPasswordChangeRequired, - checkEmailVerified, - checkEmailVerification, - checkMFAFactors, - checkUserVerification, -} from "./verify-helper"; -import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; - // Mock dependencies vi.mock("@zitadel/client", () => ({ timestampDate: vi.fn((ts: any) => new Date(Number(ts.seconds) * 1000)), diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index a8c93119a..70f3c956a 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -1,6 +1,8 @@ import { Client, create, Duration } from "@zitadel/client"; import { createServerTransport as libCreateServerTransport } from "@zitadel/client/node"; import { makeReqCtx } from "@zitadel/client/v2"; +import { otelGrpcInterceptor } from "./grpc/interceptors/otel"; +import { createLogger } from "./logger"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; import { OrganizationSchema, TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { CreateCallbackRequest, OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; @@ -35,6 +37,8 @@ import { getUserAgent } from "./fingerprint"; import { createServiceForHost } from "./service"; +const logger = createLogger("zitadel"); + const useCache = process.env.DEBUG !== "true"; async function cacheWrapper(callback: Promise) { @@ -1238,43 +1242,49 @@ export type WithServiceConfig = T & { }; export function createServerTransport(token: string, serviceConfig: ServiceConfig) { + // Build interceptors list - OTEL interceptor is always included for trace propagation + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const interceptors: any[] = [otelGrpcInterceptor]; + + // Add custom headers interceptor if needed + if (process.env.CUSTOM_REQUEST_HEADERS || serviceConfig.instanceHost || serviceConfig.publicHost) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + interceptors.push((next: any) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (req: any) => { + // Apply headers from serviceConfig + if (serviceConfig.instanceHost) { + req.header.set("x-zitadel-instance-host", serviceConfig.instanceHost); + } + if (serviceConfig.publicHost) { + req.header.set("x-zitadel-public-host", serviceConfig.publicHost); + } + + // Apply headers from CUSTOM_REQUEST_HEADERS environment variable + if (process.env.CUSTOM_REQUEST_HEADERS) { + process.env.CUSTOM_REQUEST_HEADERS.split(",").forEach((header) => { + const kv = header.indexOf(":"); + if (kv > 0) { + const key = header.slice(0, kv).trim(); + const value = header.slice(kv + 1).trim(); + if (value) { + req.header.set(key, value); + } else { + req.header.delete(key); + } + } else { + logger.warn("Skipping malformed header", { header }); + } + }); + } + + return next(req); + }; + }); + } + return libCreateServerTransport(token, { baseUrl: serviceConfig.baseUrl, - interceptors: - !process.env.CUSTOM_REQUEST_HEADERS && !serviceConfig.instanceHost && !serviceConfig.publicHost - ? undefined - : [ - (next) => { - return (req) => { - // Apply headers from serviceConfig - if (serviceConfig.instanceHost) { - req.header.set("x-zitadel-instance-host", serviceConfig.instanceHost); - } - if (serviceConfig.publicHost) { - req.header.set("x-zitadel-public-host", serviceConfig.publicHost); - } - - // Apply headers from CUSTOM_REQUEST_HEADERS environment variable - if (process.env.CUSTOM_REQUEST_HEADERS) { - process.env.CUSTOM_REQUEST_HEADERS.split(",").forEach((header) => { - const kv = header.indexOf(":"); - if (kv > 0) { - const key = header.slice(0, kv).trim(); - const value = header.slice(kv + 1).trim(); - if (value) { - req.header.set(key, value); - } else { - req.header.delete(key); - } - } else { - console.warn(`Skipping malformed header: ${header}`); - } - }); - } - - return next(req); - }; - }, - ], + interceptors, }); } diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index a3348e583..b4bf90bf6 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -3,7 +3,12 @@ import { SecuritySettings } from "@zitadel/proto/zitadel/settings/v2/security_se import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { buildCSP } from "./lib/csp"; +import { createLogger } from "./lib/logger"; import { getServiceConfig } from "./lib/service-url"; + +export const runtime = "nodejs"; + +const logger = createLogger("middleware"); export const config = { matcher: ["/.well-known/:path*", "/oauth/:path*", "/oidc/:path*", "/idps/callback/:path*", "/saml/:path*", "/:path*"], }; @@ -12,14 +17,14 @@ async function loadSecuritySettings(request: NextRequest): Promise=0.1.90'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@connectrpc/connect-node@2.1.1': resolution: {integrity: sha512-s3TfsI1XF+n+1z6MBS9rTnFsxxR4Rw5wmdEnkQINli81ESGxcsfaEet8duzq8LVuuCupmhUsgpRo0Nv9pZkufg==} engines: {node: '>=20'} @@ -1525,6 +1568,9 @@ packages: '@cypress/xvfb@1.2.4': resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -3331,10 +3377,39 @@ packages: resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.211.0': + resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} + '@opentelemetry/auto-instrumentations-node@0.69.0': + resolution: {integrity: sha512-m/wqAaeZi3VkT2izPRivEfZrvKR+cP7Y/Jkic9D8QClGFpfd3bgvfUZS+OA2MzL+RT46sO27G5TKPN+M35xQJg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.4.1 + '@opentelemetry/core': ^2.0.0 + + '@opentelemetry/configuration@0.211.0': + resolution: {integrity: sha512-PNsCkzsYQKyv8wiUIsH+loC4RYyblOaDnVASBtKS22hK55ToWs2UP6IsrcfSWWn54wWTvVe2gnfwz67Pvrxf2Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.5.0': + resolution: {integrity: sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/context-async-hooks@2.5.1': + resolution: {integrity: sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.2.0': resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3347,24 +3422,412 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.5.1': + resolution: {integrity: sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.211.0': + resolution: {integrity: sha512-UhOoWENNqyaAMP/dL1YXLkXt6ZBtovkDDs1p4rxto9YwJX1+wMjwg+Obfyg2kwpcMoaiIFT3KQIcLNW8nNGNfQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-logs-otlp-http@0.208.0': resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/exporter-logs-otlp-http@0.211.0': + resolution: {integrity: sha512-c118Awf1kZirHkqxdcF+rF5qqWwNjJh+BB1CmQvN9AQHC/DUIldy6dIkJn3EKlQnQ3HmuNRKc/nHHt5IusN7mA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.211.0': + resolution: {integrity: sha512-kMvfKMtY5vJDXeLnwhrZMEwhZ2PN8sROXmzacFU/Fnl4Z79CMrOaL7OE+5X3SObRYlDUa7zVqaXp9ZetYCxfDQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.211.0': + resolution: {integrity: sha512-D/U3G8L4PzZp8ot5hX9wpgbTymgtLZCiwR7heMe4LsbGV4OdctS1nfyvaQHLT6CiGZ6FjKc1Vk9s6kbo9SWLXQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.211.0': + resolution: {integrity: sha512-lfHXElPAoDSPpPO59DJdN5FLUnwi1wxluLTWQDayqrSPfWRnluzxRhD+g7rF8wbj1qCz0sdqABl//ug1IZyWvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.211.0': + resolution: {integrity: sha512-61iNbffEpyZv/abHaz3BQM3zUtA2kVIDBM+0dS9RK68ML0QFLRGYa50xVMn2PYMToyfszEPEgFC3ypGae2z8FA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.211.0': + resolution: {integrity: sha512-cD0WleEL3TPqJbvxwz5MVdVJ82H8jl8mvMad4bNU24cB5SH2mRW5aMLDTuV4614ll46R//R3RMmci26mc2L99g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.211.0': + resolution: {integrity: sha512-eFwx4Gvu6LaEiE1rOd4ypgAiWEdZu7Qzm2QNN2nJqPW1XDeAVH1eNwVcVQl+QK9HR/JCDZ78PZgD7xD/DBDqbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.211.0': + resolution: {integrity: sha512-F1Rv3JeMkgS//xdVjbQMrI3+26e5SXC7vXA6trx8SWEA0OUhw4JHB+qeHtH0fJn46eFItrYbL5m8j4qi9Sfaxw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.211.0': + resolution: {integrity: sha512-DkjXwbPiqpcPlycUojzG2RmR0/SIK8Gi9qWO9znNvSqgzrnAIE9x2n6yPfpZ+kWHZGafvsvA1lVXucTyyQa5Kg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.5.0': + resolution: {integrity: sha512-bk9VJgFgUAzkZzU8ZyXBSWiUGLOM3mZEgKJ1+jsZclhRnAoDNf+YBdq+G9R3cP0+TKjjWad+vVrY/bE/vRR9lA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-amqplib@0.58.0': + resolution: {integrity: sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-lambda@0.63.0': + resolution: {integrity: sha512-XEkXvrBtIKPgp6kFSuNV3FpugGiLIz3zpjXu/7t9ioBKN7pZG5hef3VCPUhtyE8UZ3N3D9rkjSLaDOND0inNrg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-aws-sdk@0.66.0': + resolution: {integrity: sha512-K+vFDsD0RsjxjCOWGOKgaqOoE5wxIPMA8wnGJ0no3m7MjVdpkS/dNOGUx2nYegpqZzU/jZ0qvc+JrfkvkzcUyg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-bunyan@0.56.0': + resolution: {integrity: sha512-cTt3gLGxBvgjgUTBeMz6MaFAHXFQM/N3411mZFTzlczuOQTlsuJTn+fWTah/a0el9NsepO5LdbULRBNmA9rSUw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cassandra-driver@0.56.0': + resolution: {integrity: sha512-56Yd41E15QlciuqC6DZR2KdeetXzhdcwp1BRRb8ORsHbRQWbvPdhV8vpvkrvs3cvY8N1KoqtPgh7mdkVhyQz+Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.54.0': + resolution: {integrity: sha512-43RmbhUhqt3uuPnc16cX6NsxEASEtn8z/cYV8Zpt6EP4p2h9s4FNuJ4Q9BbEQ2C0YlCCB/2crO1ruVz/hWt8fA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-cucumber@0.26.0': + resolution: {integrity: sha512-LGSgNR9gMJ3eiChbW9WjFgiCdJwdPKwARZwRE1s57CGY8/B3emAoQt2B05TY1y2TQuQKRBFbyNVXpWHFl9WQGQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-dataloader@0.28.0': + resolution: {integrity: sha512-ExXGBp0sUj8yhm6Znhf9jmuOaGDsYfDES3gswZnKr4MCqoBWQdEFn6EoDdt5u+RdbxQER+t43FoUihEfTSqsjA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dns@0.54.0': + resolution: {integrity: sha512-CvnGlYr8FKB2SeqauqJ7bSgZhrkVYj1vgbqFcbc/wnQcc03jc+afngkduahHiBgnJr+CYL/p3XjdKWp7AKYoGg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.59.0': + resolution: {integrity: sha512-pMKV/qnHiW/Q6pmbKkxt0eIhuNEtvJ7sUAyee192HErlr+a1Jx+FZ3WjfmzhQL1geewyGEiPGkmjjAgNY8TgDA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fastify@0.55.0': + resolution: {integrity: sha512-kkx8ODI57dN+mMW+nPuE9gniSXs/LlxWiPoXXiAJhtQJPpMqQwncHlMo+1c+qzQC5aQWkKdDskJG7TPnACNgcw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.30.0': + resolution: {integrity: sha512-n3Cf8YhG7reaj5dncGlRIU7iT40bxPOjsBEA5Bc1a1g6e9Qvb+JFJ7SEiMlPbUw4PBmxE3h40ltE8LZ3zVt6OA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.54.0': + resolution: {integrity: sha512-8dXMBzzmEdXfH/wjuRvcJnUFeWzZHUnExkmFJ2uPfa31wmpyBCMxO59yr8f/OXXgSogNgi/uPo9KW9H7LMIZ+g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.58.0': + resolution: {integrity: sha512-+yWVVY7fxOs3j2RixCbvue8vUuJ1inHxN2q1sduqDB0Wnkr4vOzVKRYl/Zy7B31/dcPS72D9lo/kltdOTBM3bQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-grpc@0.211.0': + resolution: {integrity: sha512-bshedE3TaD18OE3oPU15j8bn4vz+3X5mvg9jluoSn/ZjlshCb1FrstjNkTYQuRERWzeMl7WcR8sShr91FcUBXA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.57.0': + resolution: {integrity: sha512-Os4THbvls8cTQTVA8ApLfZZztuuqGEeqog0XUnyRW7QVF0d/vOVBEcBCk1pazPFmllXGEdNbbat8e2fYIWdFbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.211.0': + resolution: {integrity: sha512-n0IaQ6oVll9PP84SjbOCwDjaJasWRHi6BLsbMLiT6tNj7QbVOkuA5sk/EfZczwI0j5uTKl1awQPivO/ldVtsqA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.59.0': + resolution: {integrity: sha512-875UxzBHWkW+P4Y45SoFM2AR8f8TzBMD8eO7QXGCyFSCUMP5s9vtt/BS8b/r2kqLyaRPK6mLbdnZznK3XzQWvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.20.0': + resolution: {integrity: sha512-yJXOuWZROzj7WmYCUiyT27tIfqBrVtl1/TwVbQyWPz7rL0r1Lu7kWjD0PiVeTCIL6CrIZ7M2s8eBxsTAOxbNvw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.55.0': + resolution: {integrity: sha512-FtTL5DUx5Ka/8VK6P1VwnlUXPa3nrb7REvm5ddLUIeXXq4tb9pKd+/ThB1xM/IjefkRSN3z8a5t7epYw1JLBJQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.59.0': + resolution: {integrity: sha512-K9o2skADV20Skdu5tG2bogPKiSpXh4KxfLjz6FuqIVvDJNibwSdu5UvyyBzRVp1rQMV6UmoIk6d3PyPtJbaGSg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.55.0': + resolution: {integrity: sha512-FDBfT7yDGcspN0Cxbu/k8A0Pp1Jhv/m7BMTzXGpcb8ENl3tDj/51U65R5lWzUH15GaZA15HQ5A5wtafklxYj7g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-memcached@0.54.0': + resolution: {integrity: sha512-7lG+XMQVt8I+/qc4U0KAwabnIAn4CubmxBPftlrChmcok6wbv6z6W+SCVNBbN13FvPgum8NO0YwyuUXMmCyXvg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.64.0': + resolution: {integrity: sha512-pFlCJjweTqVp7B220mCvCld1c1eYKZfQt1p3bxSbcReypKLJTwat+wbL2YZoX9jPi5X2O8tTKFEOahO5ehQGsA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.57.0': + resolution: {integrity: sha512-MthiekrU/BAJc5JZoZeJmo0OTX6ycJMiP6sMOSRTkvz5BrPMYDqaJos0OgsLPL/HpcgHP7eo5pduETuLguOqcg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.57.0': + resolution: {integrity: sha512-nHSrYAwF7+aV1E1V9yOOP9TchOodb6fjn4gFvdrdQXiRE7cMuffyLLbCZlZd4wsspBzVwOXX8mpURdRserAhNA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.57.0': + resolution: {integrity: sha512-HFS/+FcZ6Q7piM7Il7CzQ4VHhJvGMJWjx7EgCkP5AnTntSN5rb5Xi3TkYJHBKeR27A0QqPlGaCITi93fUDs++Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-nestjs-core@0.57.0': + resolution: {integrity: sha512-mzTjjethjuk70o/vWUeV12QwMG9EAFJpkn13/q8zi++sNosf2hoGXTplIdbs81U8S3PJ4GxHKsBjM0bj1CGZ0g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-net@0.55.0': + resolution: {integrity: sha512-J7isLTAmBphAKX99fZgR/jYFRJk+d5E3yVDEd7eTcyPPwFDN/LM8J8j/H5gP4ukZCbt0mtKnx1CA+P5+qw7xFQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-openai@0.9.0': + resolution: {integrity: sha512-Tf3shDZZo3pKz0LBschaEfX+SgpwMITnm8moOMzr6Fc10sKU96GxFwMmEg2JC0JW5x56kGJuwRoXZCVL66GBgg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-oracledb@0.36.0': + resolution: {integrity: sha512-VyfdaRfr/xnx/ndQnCCk34z7HqADxmRi47SLTzL9m79LrA+F1qK49nCcqbeiFfeVJ2RA5NmfSS+BllFE4RGnsw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.63.0': + resolution: {integrity: sha512-dKm/ODNN3GgIQVlbD6ZPxwRc3kleLf95hrRWXM+l8wYo+vSeXtEpQPT53afEf6VFWDVzJK55VGn8KMLtSve/cg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pino@0.57.0': + resolution: {integrity: sha512-Oa+PT1fxWQo88KSfibLJSyCwdV9Kb2iqjpIbfMK5CFcyeOGfth8mVSFjvQEaCo+Tdbpq9Y8Ylyi4/XmWrxStew==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.59.0': + resolution: {integrity: sha512-JKv1KDDYA2chJ1PC3pLP+Q9ISMQk6h5ey+99mB57/ARk0vQPGZTTEb4h4/JlcEpy7AYT8HIGv7X6l+br03Neeg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-restify@0.56.0': + resolution: {integrity: sha512-ZkPT7zoIx6du3u7Js4n7FEw1FvNdeIpprpcM0pR4p7kfgQ82ZzhfJ7ilWKxT9Hpe6HMu+yFLicFyS1b83XcVMQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-router@0.55.0': + resolution: {integrity: sha512-8IA64a6+vVQavH1qj2W/0mPOr1uS6ROkLoV29p+3At2omEIgn13g46yslKqU5lIgMSn9uzU4tSlOTe6vQM4dIg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-runtime-node@0.24.0': + resolution: {integrity: sha512-1gNjTpHhgHIkRXivY4Nk+jS+2oChwQSnEVne4AHvlY0tzLHpWE+LEZV6DoiN7Ui93/UpnebhMsF0YUnFZaeJdg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-socket.io@0.57.0': + resolution: {integrity: sha512-0FhO9/UPnOsRbbVHLxgffXMEdATNJQauwM+X4+X6UaV9EANEhci+etMX9R06xprJRvE3kDcfXoMn2MTF3RdNDw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.30.0': + resolution: {integrity: sha512-bZy9Q8jFdycKQ2pAsyuHYUHNmCxCOGdG6eg1Mn75RvQDccq832sU5OWOBnc12EFUELI6icJkhR7+EQKMBam2GA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.21.0': + resolution: {integrity: sha512-gok0LPUOTz2FQ1YJMZzaHcOzDFyT64XJ8M9rNkugk923/p6lDGms/cRW1cqgqp6N6qcd6K6YdVHwPEhnx9BWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation-winston@0.55.0': + resolution: {integrity: sha512-RKW/PYJrvIbRYss0uKe0eU+FgIRScnQTJXIWAZK17ViHf7EALaRDXOu3tFW5JDRg6fkccj5q90YZUCzh6s0v5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.211.0': + resolution: {integrity: sha512-h0nrZEC/zvI994nhg7EgQ8URIHt0uDTwN90r3qQUdZORS455bbx+YebnGeEuFghUT0HlJSrLF4iHw67f+odY+Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.208.0': resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.211.0': + resolution: {integrity: sha512-bp1+63V8WPV+bRI9EQG6E9YID1LIHYSZVbp7f+44g9tRzCq+rtw/o4fpL5PC31adcUsFiz/oN0MdLISSrZDdrg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.211.0': + resolution: {integrity: sha512-mR5X+N4SuphJeb7/K7y0JNMC8N1mB6gEtjyTLv+TSAhl0ZxNQzpSKP8S5Opk90fhAqVYD4R0SQSAirEBlH1KSA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.208.0': resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-transformer@0.211.0': + resolution: {integrity: sha512-julhCJ9dXwkOg9svuuYqqjXLhVaUgyUvO2hWbTxwjvLXX2rG3VtAaB0SzxMnGTuoCZizBT7Xqqm2V7+ggrfCXA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.5.0': + resolution: {integrity: sha512-g10m4KD73RjHrSvUge+sUxUl8m4VlgnGc6OKvo68a4uMfaLjdFU+AULfvMQE/APq38k92oGUxEzBsAZ8RN/YHg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.5.0': + resolution: {integrity: sha512-t70ErZCncAR/zz5AcGkL0TF25mJiK1FfDPEQCgreyAHZ+mRJ/bNUiCnImIBDlP3mSDXy6N09DbUEKq0ktW98Hg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.2': + resolution: {integrity: sha512-EaS54zwYmOg9Ttc79juaktpCBYqyh2IquXl534sLls+c1/pc8LZfWPMqytFt+iBvSPQ6ajraUnvi6cun4AhSjQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-aws@2.12.0': + resolution: {integrity: sha512-VelueKblsnQEiBVqEYcvM9VEb+B8zN6nftltdO9HAD7qi/OlicP4z/UGJ9EeW2m++WabdMoj0G3QVL8YV0P9tw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-azure@0.19.0': + resolution: {integrity: sha512-3UBJYyAfQY7aqot4xBvTsGlxi9Ax5XwWlddCvFPNIfZiy5KX405w3KThcRypadVsP5Q9D/lr/WAn5J+xXTqJoA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-container@0.8.3': + resolution: {integrity: sha512-5J0JP2cy655rBKM9Doz26ffO3rG+Xqm7OXeNXkckzmc3JmL6Bj3dPBKugPYsfemhEIqtf7INH9UmPQqTMuWoHg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/resource-detector-gcp@0.46.0': + resolution: {integrity: sha512-CulcNXV/a4lc4TTYFdApTfRg4DlCwiUilsXnEsRfFSK/p/EbkfgEQz8hB4tZF5z/Us9MnhtuT6l4Kj4Ng8qLcw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@opentelemetry/resources@2.2.0': resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3377,28 +3840,86 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.5.1': + resolution: {integrity: sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-logs@0.208.0': resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-logs@0.211.0': + resolution: {integrity: sha512-O5nPwzgg2JHzo59kpQTPUOTzFi0Nv5LxryG27QoXBciX3zWM3z83g+SNOHhiQVYRWFSxoWn1JM2TGD5iNjOwdA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.2.0': resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' + '@opentelemetry/sdk-metrics@2.5.0': + resolution: {integrity: sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.211.0': + resolution: {integrity: sha512-+s1eGjoqmPCMptNxcJJD4IxbWJKNLOQFNKhpwkzi2gLkEbCj6LzSHJNhPcLeBrBlBLtlSpibM+FuS7fjZ8SSFQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.2.0': resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.5.0': + resolution: {integrity: sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.5.1': + resolution: {integrity: sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.5.0': + resolution: {integrity: sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.5.1': + resolution: {integrity: sha512-9lopQ6ZoElETOEN0csgmtEV5/9C7BMfA7VtF4Jape3i954b6sTY2k3Xw3CxUTKreDck/vpAuJM+EDo4zheUw+A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.39.0': resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} engines: {node: '>=14'} + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + + '@opentelemetry/winston-transport@0.21.0': + resolution: {integrity: sha512-qdWUVX/DtCS9MbxkEq3u96dTBWBEVjYUcwj9EPi+nVyZV7ZXa2Kn+2zD7BfqR+aJahb2KUIm4HDzR8poEdo/Xg==} + engines: {node: ^18.19.0 || >=20.6.0} + '@orama/orama@3.1.18': resolution: {integrity: sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA==} engines: {node: '>= 20.0.0'} @@ -4800,6 +5321,9 @@ packages: '@sinclair/typebox@0.34.41': resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -5065,6 +5589,9 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/aws-lambda@8.10.160': + resolution: {integrity: sha512-uoO4QVQNWFPJMh26pXtmtrRfGshPUSpMZGUyUQY20FhfHEElEBOPKgVmFs1z+kbpyBsRs2JnoOPT7++Z4GA9pA==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -5077,12 +5604,18 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/bunyan@1.8.11': + resolution: {integrity: sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==} + '@types/codemirror@5.60.16': resolution: {integrity: sha512-V/yHdamffSS075jit+fDxaOAmdP2liok8NSNJnAZfDJErzOheuygHZEhAJrfmk5TEyM32MhkZjwo/idX791yxw==} '@types/concat-stream@2.0.3': resolution: {integrity: sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} @@ -5155,9 +5688,15 @@ packages: '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/memcached@2.2.10': + resolution: {integrity: sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -5185,6 +5724,15 @@ packages: '@types/opentype.js@1.3.9': resolution: {integrity: sha512-KOGywvDPncA4/tTWV5xKNhjpsoSSAHIx3mHOhL5l3XX+c6Xu2dQnHvGs7mRNQsQRte1EqmQ0cPQQ8Z14lkv+yw==} + '@types/oracledb@6.5.2': + resolution: {integrity: sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==} + + '@types/pg-pool@2.0.7': + resolution: {integrity: sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} @@ -5223,12 +5771,18 @@ packages: '@types/supports-color@8.1.3': resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/tern@0.23.9': resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==} '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -5798,6 +6352,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -6158,6 +6717,9 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -6360,6 +6922,9 @@ packages: resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} engines: {node: '>=8'} + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -6434,9 +6999,25 @@ packages: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -6906,6 +7487,9 @@ packages: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -7421,6 +8005,9 @@ packages: picomatch: optional: true + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -7491,6 +8078,9 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + focus-trap@7.6.6: resolution: {integrity: sha512-v/Z8bvMCajtx4mEXmOo7QEsIzlIOqRXTIwgUfsFOF9gEsespdbD0AkPIka1bSXZ8Y8oZ+2IVDQZePkTfEHZl7Q==} @@ -7537,6 +8127,9 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -7747,10 +8340,18 @@ packages: resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} engines: {node: '>=10'} + gaxios@6.7.1: + resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} + engines: {node: '>=14'} + gaxios@7.1.3: resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} + gcp-metadata@6.1.1: + resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} + engines: {node: '>=14'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -7871,6 +8472,10 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + google-logging-utils@0.0.2: + resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + engines: {node: '>=14'} + google-protobuf@3.21.4: resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} @@ -8149,6 +8754,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -8582,6 +9190,9 @@ packages: engines: {node: '>=6'} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -8714,6 +9325,9 @@ packages: '@types/node': '>=18' typescript: '>=5.0.4 <7' + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -8949,6 +9563,10 @@ packages: resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} engines: {node: '>=8.0'} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -9369,6 +9987,9 @@ packages: peerDependencies: mocha: '>=7' + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} @@ -9730,6 +10351,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -9964,6 +10588,17 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.11.0: + resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -10099,6 +10734,22 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + posthog-js@1.336.1: resolution: {integrity: sha512-YphbVhXnImmZoALvf2oh129Cxu6IRQ9P9sWhuyY+dGe7jqt1jBp6Dg7QEK39stB4rzxmT/N3OLFcWZM7ZYQzCg==} @@ -10257,6 +10908,10 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} + protobufjs@8.0.0: + resolution: {integrity: sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -10585,6 +11240,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -10695,6 +11354,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -10762,6 +11425,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -10951,6 +11617,9 @@ packages: stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -11252,6 +11921,9 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -11356,6 +12028,10 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -11717,6 +12393,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + uvu@0.5.6: resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} engines: {node: '>=8'} @@ -12031,6 +12711,14 @@ packages: engines: {node: '>=8'} hasBin: true + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -12092,6 +12780,10 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -13254,6 +13946,8 @@ snapshots: '@colors/colors@1.5.0': {} + '@colors/colors@1.6.0': {} + '@connectrpc/connect-node@2.1.1(@bufbuild/protobuf@2.11.0)(@connectrpc/connect@2.1.1(@bufbuild/protobuf@2.11.0))': dependencies: '@bufbuild/protobuf': 2.11.0 @@ -13335,6 +14029,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -13674,7 +14374,7 @@ snapshots: dependencies: '@eslint/object-schema': 3.0.1 debug: 4.4.3(supports-color@8.1.1) - minimatch: 10.1.1 + minimatch: 10.2.1 transitivePeerDependencies: - supports-color @@ -14886,8 +15586,83 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs@0.211.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api@1.9.0': {} + '@opentelemetry/auto-instrumentations-node@0.69.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-aws-lambda': 0.63.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-aws-sdk': 0.66.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-bunyan': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-cassandra-driver': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-cucumber': 0.26.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dns': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fastify': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.58.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.20.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-memcached': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.64.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-nestjs-core': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-net': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-openai': 0.9.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-oracledb': 0.36.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.63.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pino': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.59.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-restify': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-router': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-runtime-node': 0.24.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-socket.io': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-winston': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-alibaba-cloud': 0.33.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-aws': 2.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-azure': 0.19.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-container': 0.8.3(@opentelemetry/api@1.9.0) + '@opentelemetry/resource-detector-gcp': 0.46.0(@opentelemetry/api@1.9.0)(encoding@0.1.13) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - encoding + - supports-color + + '@opentelemetry/configuration@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + yaml: 2.8.1 + + '@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/context-async-hooks@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14898,6 +15673,21 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/core@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14907,12 +15697,479 @@ snapshots: '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-metrics-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-prometheus@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-lambda@0.63.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/aws-lambda': 8.10.160 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-aws-sdk@0.66.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-bunyan@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@types/bunyan': 1.8.11 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cassandra-driver@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-cucumber@0.26.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.28.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dns@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fastify@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.58.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-grpc@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.20.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-memcached@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/memcached': 2.2.10 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.64.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-nestjs-core@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-net@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-openai@0.9.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-oracledb@0.36.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/oracledb': 6.5.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.63.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.7 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pino@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.59.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-restify@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-router@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-runtime-node@0.24.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-socket.io@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.30.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.21.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-winston@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + import-in-the-middle: 2.0.6 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14924,6 +16181,65 @@ snapshots: '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) protobufjs: 7.5.4 + '@opentelemetry/otlp-transformer@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + protobufjs: 8.0.0 + + '@opentelemetry/propagator-b3@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/redis-common@0.38.2': {} + + '@opentelemetry/resource-detector-alibaba-cloud@0.33.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/resource-detector-aws@2.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/resource-detector-azure@0.19.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/resource-detector-container@0.8.3(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/resource-detector-gcp@0.46.0(@opentelemetry/api@1.9.0)(encoding@0.1.13)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + gcp-metadata: 6.1.1(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - supports-color + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14936,6 +16252,12 @@ snapshots: '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/resources@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14943,12 +16265,55 @@ snapshots: '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.211.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.211.0 + '@opentelemetry/configuration': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-metrics-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-prometheus': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -14956,8 +16321,46 @@ snapshots: '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-base@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-node@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-node@2.5.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions@1.39.0': {} + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/winston-transport@0.21.0': + dependencies: + '@opentelemetry/api-logs': 0.211.0 + winston-transport: 4.9.0 + '@orama/orama@3.1.18': {} '@otplib/core@12.0.1': {} @@ -16663,6 +18066,11 @@ snapshots: '@sinclair/typebox@0.34.41': {} + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.1.0': {} @@ -16879,7 +18287,7 @@ snapshots: '@tufjs/models@4.1.0': dependencies: '@tufjs/canonical-json': 2.0.0 - minimatch: 10.1.1 + minimatch: 10.2.1 '@tybys/wasm-util@0.10.1': dependencies: @@ -16892,6 +18300,8 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/aws-lambda@8.10.160': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -16913,6 +18323,10 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@types/bunyan@1.8.11': + dependencies: + '@types/node': 25.2.3 + '@types/codemirror@5.60.16': dependencies: '@types/tern': 0.23.9 @@ -16921,6 +18335,10 @@ snapshots: dependencies: '@types/node': 25.2.3 + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.2.3 + '@types/cors@2.8.19': dependencies: '@types/node': 24.10.9 @@ -16997,8 +18415,16 @@ snapshots: '@types/mdx@2.0.13': {} + '@types/memcached@2.2.10': + dependencies: + '@types/node': 25.2.3 + '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': + dependencies: + '@types/node': 25.2.3 + '@types/node-fetch@2.6.13': dependencies: '@types/node': 25.2.3 @@ -17030,6 +18456,20 @@ snapshots: '@types/opentype.js@1.3.9': {} + '@types/oracledb@6.5.2': + dependencies: + '@types/node': 25.2.3 + + '@types/pg-pool@2.0.7': + dependencies: + '@types/pg': 8.15.6 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 25.2.3 + pg-protocol: 1.11.0 + pg-types: 2.2.0 + '@types/prismjs@1.26.5': {} '@types/prop-types@15.7.15': {} @@ -17073,12 +18513,18 @@ snapshots: '@types/supports-color@8.1.3': {} + '@types/tedious@4.0.14': + dependencies: + '@types/node': 25.2.3 + '@types/tern@0.23.9': dependencies: '@types/estree': 1.0.8 '@types/tinycolor2@1.4.6': {} + '@types/triple-beam@1.3.5': {} + '@types/trusted-types@2.0.7': optional: true @@ -17975,6 +19421,10 @@ snapshots: mime-types: 3.0.1 negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-import-phases@1.0.4(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -18373,6 +19823,8 @@ snapshots: big.js@5.2.2: {} + bignumber.js@9.3.1: {} + binary-extensions@2.3.0: {} bl@4.1.0: @@ -18585,6 +20037,8 @@ snapshots: ci-info@4.3.0: {} + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -18659,8 +20113,23 @@ snapshots: dependencies: color-name: 1.1.4 + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + color-name@1.1.4: {} + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + colorette@2.0.20: {} colorjs.io@0.5.2: {} @@ -19128,6 +20597,8 @@ snapshots: emojis-list@3.0.0: {} + enabled@2.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -19459,7 +20930,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.56.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) @@ -19573,6 +21044,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.56.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + dependencies: + debug: 3.2.7(supports-color@8.1.1) + optionalDependencies: + '@typescript-eslint/parser': 8.56.0(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 @@ -19631,6 +21113,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.56.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.56.0(eslint@8.57.1)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@10.0.0(jiti@2.6.1)): dependencies: aria-query: 5.3.2 @@ -20113,6 +21624,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fecha@4.2.3: {} + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -20202,6 +21715,8 @@ snapshots: flatted@3.3.3: {} + fn.name@1.1.0: {} + focus-trap@7.6.6: dependencies: tabbable: 6.3.0 @@ -20246,6 +21761,8 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded-parse@2.1.2: {} + forwarded@0.2.0: {} fraction.js@5.3.4: {} @@ -20466,6 +21983,17 @@ snapshots: fuse.js@7.1.0: {} + gaxios@6.7.1(encoding@0.1.13): + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + is-stream: 2.0.1 + node-fetch: 2.7.0(encoding@0.1.13) + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + gaxios@7.1.3: dependencies: extend: 3.0.2 @@ -20475,6 +22003,15 @@ snapshots: transitivePeerDependencies: - supports-color + gcp-metadata@6.1.1(encoding@0.1.13): + dependencies: + gaxios: 6.7.1(encoding@0.1.13) + google-logging-utils: 0.0.2 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -20606,6 +22143,8 @@ snapshots: globrex@0.1.2: {} + google-logging-utils@0.0.2: {} + google-protobuf@3.21.4: {} gopd@1.2.0: {} @@ -20959,7 +22498,7 @@ snapshots: ignore-walk@8.0.0: dependencies: - minimatch: 10.1.1 + minimatch: 10.2.1 ignore@5.3.2: {} @@ -20979,6 +22518,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + import-meta-resolve@4.2.0: {} imurmurhash@0.1.4: {} @@ -21397,6 +22943,10 @@ snapshots: jsesc@3.1.0: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-buffer@3.0.1: {} json-parse-even-better-errors@2.3.1: {} @@ -21578,6 +23128,8 @@ snapshots: typescript: 5.9.3 zod: 4.3.6 + kuler@2.0.0: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -21826,6 +23378,15 @@ snapshots: transitivePeerDependencies: - supports-color + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + long@5.3.2: {} longest-streak@3.1.0: {} @@ -22531,6 +24092,8 @@ snapshots: strip-ansi: 6.0.1 uuid: 8.3.2 + module-details-from-path@1.0.4: {} + moment@2.30.1: {} motion-dom@12.34.0: @@ -22974,6 +24537,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -23272,6 +24839,18 @@ snapshots: performance-now@2.1.0: {} + pg-int8@1.0.1: {} + + pg-protocol@1.11.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -23376,6 +24955,16 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + posthog-js@1.336.1: dependencies: '@opentelemetry/api': 1.9.0 @@ -23492,6 +25081,21 @@ snapshots: '@types/node': 24.10.9 long: 5.3.2 + protobufjs@8.0.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.2.3 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -23929,6 +25533,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + require-main-filename@2.0.0: {} requires-port@1.0.0: {} @@ -24062,6 +25673,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sass@1.90.0: @@ -24147,6 +25760,8 @@ snapshots: transitivePeerDependencies: - supports-color + server-only@0.0.1: {} + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -24432,6 +26047,8 @@ snapshots: dependencies: stackframe: 1.3.4 + stack-trace@0.0.10: {} + stackback@0.0.2: {} stackframe@1.3.4: {} @@ -24822,6 +26439,8 @@ snapshots: transitivePeerDependencies: - react-native-b4a + text-hex@1.0.0: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -24900,6 +26519,8 @@ snapshots: trim-lines@3.0.1: {} + triple-beam@1.4.1: {} + trough@2.2.0: {} truncate-json@3.0.1: @@ -25316,6 +26937,8 @@ snapshots: uuid@8.3.2: {} + uuid@9.0.1: {} + uvu@0.5.6: dependencies: dequal: 2.0.3 @@ -25765,6 +27388,26 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + word-wrap@1.2.5: {} workerpool@9.3.4: {} @@ -25807,6 +27450,8 @@ snapshots: xmlchars@2.2.0: {} + xtend@4.0.2: {} + y18n@4.0.3: {} y18n@5.0.8: {}