corpus

Log | Files | Refs | README | LICENSE

commit 06522c9ec707bc36a8d393f6a1bf7422818d47e2
parent f87b8b51bf8d2ce40046c7ac826349e96f9af530
Author: mtmn <miro@haravara.org>
Date:   Sun, 19 Apr 2026 13:22:57 +0200

feat!: remove otel, add cosine.club metrics

Diffstat:
MREADME.md | 2--
Mdocs/architecture.md | 10++--------
Mflake.lock | 6+++---
Mflake.nix | 4++--
Mpackage-lock.json | 920+------------------------------------------------------------------------------
Mpackage.json | 9++-------
Msrc/Main.purs | 27++++++++++++++++++---------
Msrc/Metrics.js | 92++++++++++++++-----------------------------------------------------------------
Msrc/Metrics.purs | 8++++----
9 files changed, 48 insertions(+), 1030 deletions(-)

diff --git a/README.md b/README.md @@ -60,7 +60,5 @@ npx spago bundle --module Client --outfile client.js --platform browser | `AWS_ENDPOINT_URL` | — | S3-compatible endpoint (e.g. for MinIO) | | `AWS_S3_ADDRESSING_STYLE` | — | Set to `path` for path-style S3 URLs | | `METRICS_ENABLED` | `false` | Set to `true` to expose Prometheus metrics at `/metrics` | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | If set, enables OpenTelemetry tracing and exports spans to this OTLP HTTP endpoint | -| `OTEL_SERVICE_NAME` | `corpus` | Service name reported in OTel spans | Per-user settings (`listenbrainzUser`, `lastfmUser`, `initialSync`, `coverCacheEnabled`, `backupEnabled`, `backupIntervalHours`, etc.) are configured in `users.json` (or `users.dhall`), not via environment variables. diff --git a/docs/architecture.md b/docs/architecture.md @@ -11,7 +11,7 @@ The server is built with PureScript running on Node.js. It handles several core - **Last.fm Sync**: A background process that polls the Last.fm API every 60 seconds to fetch new scrobbles. Both syncs write to the same `scrobbles` table; duplicate timestamps are silently ignored. - **Metadata Enrichment**: A background process that identifies scrobbles with missing metadata (genres, labels, release years) and fetches information from MusicBrainz, Last.fm, and Discogs. - **Cover Art Proxy**: A specialized endpoint that fetches, caches, and serves cover art, utilizing a multi-source fallback strategy (CAA → Last.fm → Discogs). -- **Observability**: Prometheus metrics exposed at `/metrics` and optional OpenTelemetry tracing of all HTTP requests. +- **Observability**: Prometheus metrics exposed at `/metrics`. ### Frontend A Single Page Application (SPA) built with [Elm](https://elm-lang.org). @@ -67,8 +67,6 @@ Each user gets their own `UserContext` with an independent DuckDB connection, sy | `AWS_S3_ADDRESSING_STYLE` | — | `virtual` or `path` | | `PORT` | `8000` | HTTP listen port | | `METRICS_ENABLED` | `false` | Set to `true` to enable the Prometheus `/metrics` endpoint | -| `OTEL_EXPORTER_OTLP_ENDPOINT` | — | If set, enables OpenTelemetry tracing and exports spans to this OTLP HTTP endpoint | -| `OTEL_SERVICE_NAME` | `corpus` | Service name reported in OTel spans | ### users.dhall Fields @@ -137,10 +135,6 @@ Prometheus metrics are **disabled by default**. Set `METRICS_ENABLED=true` to en Node.js default metrics (GC, event loop, memory) are also collected via `prom-client`'s `collectDefaultMetrics`. -### OpenTelemetry Tracing - -Tracing is opt-in: the OTel SDK starts only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Spans are exported over OTLP/HTTP. Each incoming HTTP request becomes a server span; outbound `fetch`/`undici` calls made during that request automatically become child spans via the `HttpInstrumentation` and `UndiciInstrumentation` auto-instrumentation packages. W3C `traceparent` headers are extracted from incoming requests for distributed trace propagation. - ## Tech Stack - **Language**: [PureScript](https://purescript.org) (server), [Elm](https://elm-lang.org) (frontend) @@ -158,7 +152,7 @@ Corpus relies on FFI to interact with the Node.js ecosystem where native PureScr - **Cloud Storage (`S3.js`)**: AWS SDK (`@aws-sdk/client-s3`) for cover art caching. Takes explicit config structs rather than reading `process.env`. - **System Utilities (`Main.js`)**: Bridges PureScript with Node.js — `dotenv` loading and request helpers. - **Config (`Config.js`)**: Reads and parses `users.json` from the path given by `CORPUS_USERS_FILE`. -- **Observability (`Metrics.js`)**: Initialises `prom-client` (Prometheus) and the OpenTelemetry Node SDK. Exports metric-increment helpers called from PureScript and the `wrapRequest` function that wraps each HTTP handler in an OTel server span. +- **Observability (`Metrics.js`)**: Initialises `prom-client` (Prometheus). Exports metric-increment helpers called from PureScript and the `wrapRequest` function that records metrics and logs each HTTP request. ## System Flow diff --git a/flake.lock b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776255774, - "narHash": "sha256-psVTpH6PK3q1htMJpmdz1hLF5pQgEshu7gQWgKO6t6Y=", + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "566acc07c54dc807f91625bb286cb9b321b5f42a", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix @@ -128,10 +128,10 @@ corpus = pkgs.buildNpmPackage { pname = "corpus"; - version = (builtins.fromJSON (builtins.readFile ./package.json)).version; + inherit ((builtins.fromJSON (builtins.readFile ./package.json))) version; inherit src; - npmDepsHash = "sha256-gBFhiRVXLRj+x0K2dj+JW9OOc2dEP97PoVkuchabWVk="; + npmDepsHash = "sha256-u+Z0zdHCfU/RXmK10g4IT/EJhnp3oG/BHP/yauQeviU="; npmRebuildFlags = ["--ignore-scripts"]; nativeBuildInputs = with pkgs; [ diff --git a/package-lock.json b/package-lock.json @@ -9,11 +9,6 @@ "version": "1.4.0", "dependencies": { "@aws-sdk/client-s3": "^3.1029.0", - "@opentelemetry/api": "^1.9.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.215.0", - "@opentelemetry/instrumentation-http": "^0.215.0", - "@opentelemetry/instrumentation-undici": "^0.25.0", - "@opentelemetry/sdk-node": "^0.215.0", "dotenv": "^17.4.1", "duckdb": "^1.4.4", "prom-client": "^15.1.3" @@ -1329,61 +1324,6 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "license": "MIT" }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/proto-loader/node_modules/protobufjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", - "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "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": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/@isaacs/cliui": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", @@ -1415,16 +1355,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/@mapbox/node-pre-gyp": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", @@ -1520,610 +1450,6 @@ "node": ">=8.0.0" } }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.215.0.tgz", - "integrity": "sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/configuration": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.215.0.tgz", - "integrity": "sha512-FSWvDryxjinHROfzEVbJGBw10FqGzLEm2C1LPX6Lot6hvxq3lFJzNLlue8vm64C5yIbqSQVjWsPhYu56ThQS4Q==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "yaml": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.0.tgz", - "integrity": "sha512-MWXggArM+Y11mPS8VOrqxOj+YMGQSRuvhM91eSBX4xFpJa05mpkeVvM8pPux5ElkEjV5RMgrkisrlP/R83SpBQ==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", - "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.215.0.tgz", - "integrity": "sha512-MVq+9ma/63XRXc0AcnS+XyWSD6VBYn39OucsvpzjqxTpzTOiGXNxTwsbV3zbnvgUexb5hc2ZjJlZUK2W/19UUw==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/sdk-logs": "0.215.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.215.0.tgz", - "integrity": "sha512-U7Qb+TVX2GZH5RSC+Gx9aE5zChKP1kPg87X3PlI/41lWVPJdBIzmgMmuE28MmQlrK84nLHCIqUOOben8YkSzBw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/sdk-logs": "0.215.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.215.0.tgz", - "integrity": "sha512-vs2xKKTdt/vKWMuBzw+LZYYCKqulodCRoonWWiyToIQfa6JgbyWjTu/iy6qpBLhLi+t6fNc1bwJGwu3vkot2Jg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-logs": "0.215.0", - "@opentelemetry/sdk-trace-base": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.215.0.tgz", - "integrity": "sha512-1TAMliHQvzc+v1OtnLMHSk5sU8BSkJbxIKrWzuCWcQjajWrvem/r5ugLK6agI0PjPz/ADfZju5AVYedlNyeO9g==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.215.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-metrics": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.215.0.tgz", - "integrity": "sha512-FRydO5j7MWnXK9ghfykKxiSM8I5UeiicK/UNl3/mv86xoEKkb+LKz1I3WXgkuYVOQf22VNqbPO58s2W1mVWtEQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-metrics": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.215.0.tgz", - "integrity": "sha512-d8/Sys9MtxLbn0S+RE1pUNcuoI9ZyI4SPfOO+yskSEQiPFoKCTMwwthB8MTY4S8qxCBAWyM+P7QMX+vEIT7PZw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.215.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-metrics": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-prometheus": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.215.0.tgz", - "integrity": "sha512-7ghCl1G84jccmxG3B8UwUMZ1OlequBzB1jt5tZ4DDiAyVKeA4Roz5D6VK8SQ0ZyBQffVyX/rtXrpVXKVzRCGfg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-metrics": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.215.0.tgz", - "integrity": "sha512-+SuWfPFVjPTvHJhlzTCBetLsPVu86xSFPR3fv8TN+H7lpe5aZzF96TUsfMHDR0lwpIwlJpG57CJnGalIfrpXkg==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.215.0.tgz", - "integrity": "sha512-k4J9ISeGpb0Bm/wCrlcrbroMFTkiWMrdhNxQGrlktxLy127Yzd4/7nrTawn5d/ApktYTknvdixsE6++34Qfi1w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.215.0.tgz", - "integrity": "sha512-+QclHuJmlp/I3Z2fNn+j1dAajMjJqJ4Sgo8ajwiK6Tzmg5SNwBGmBX66AZvTLe/3/bc3L7bo90m9gsaJBrzEsA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.0.tgz", - "integrity": "sha512-tbzcYDmZWtX4hgJn15qP7/iYFVd1yzbUloBuSYsQtn0XQTxJsG7vgwkPKEBellriH0XJmlZJxYtWkHpwzHBhaQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.215.0.tgz", - "integrity": "sha512-SyJONuqypQ2xWdYMy99vF7JhZ2kDTGx4oRmM/jZV+kRtZ96JTnJmEINbIJgHz7Gnhtw0bimHwbPy/pguA5wpPQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "import-in-the-middle": "^3.0.0", - "require-in-the-middle": "^8.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.215.0.tgz", - "integrity": "sha512-ip9iNoRRVxDyP8LVfdqqI6OwbOwzxTl4SaP1WDKJq0sDsgpOr7rIOFj7gV8yKl4F5PdDOUYy8VqdgIOWZRlGBw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/instrumentation": "0.215.0", - "@opentelemetry/semantic-conventions": "^1.29.0", - "forwarded-parse": "2.1.2" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.25.0.tgz", - "integrity": "sha512-yPc3sZ3gwlxArBoW0LXpyE0GA4gORSajFBuME2jAo3YXgwSMI86SmQwFhYlRlYgx9LPWH8LIMDZ4J7cYFfyaBg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.215.0", - "@opentelemetry/semantic-conventions": "^1.24.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.215.0.tgz", - "integrity": "sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-transformer": "0.215.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.215.0.tgz", - "integrity": "sha512-WkuHkUrhwNxTKrm7Xuf6S+HmLNbk2T8S2YiZhN606RfgetSQb9xLp4NizWLwXvw63uxGsBaK262dirFO2yht2g==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/otlp-transformer": "0.215.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.215.0.tgz", - "integrity": "sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-logs": "0.215.0", - "@opentelemetry/sdk-metrics": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0", - "protobufjs": "^8.0.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.7.0.tgz", - "integrity": "sha512-HNm+tdXY5i8dzAo4YankchNWdZ4Z1Boop7lhbb3wltWT0MwEMo0QADRJwrF83pXEeDT+5Bmq4J8sStFaUywE3g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.0.tgz", - "integrity": "sha512-lKMAjekRkFYWrjmPTaxUJt+V8Mr1iB94sP3HDZZCmdZ/LUV/wtqAGqXhgnkIbdlnWxxvEs9MGEIMdJC+xObMFg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", - "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.215.0.tgz", - "integrity": "sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.0.tgz", - "integrity": "sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node": { - "version": "0.215.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.215.0.tgz", - "integrity": "sha512-YunKvZOMhYNMBJ66YRjbGShuoV/w1y21U7MGPRx0iPJenPszOddtYEQFJv8piAEOn94BUFIfJHtHjptrHsGiIA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.215.0", - "@opentelemetry/configuration": "0.215.0", - "@opentelemetry/context-async-hooks": "2.7.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.215.0", - "@opentelemetry/exporter-logs-otlp-http": "0.215.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.215.0", - "@opentelemetry/exporter-metrics-otlp-grpc": "0.215.0", - "@opentelemetry/exporter-metrics-otlp-http": "0.215.0", - "@opentelemetry/exporter-metrics-otlp-proto": "0.215.0", - "@opentelemetry/exporter-prometheus": "0.215.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.215.0", - "@opentelemetry/exporter-trace-otlp-http": "0.215.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.215.0", - "@opentelemetry/exporter-zipkin": "2.7.0", - "@opentelemetry/instrumentation": "0.215.0", - "@opentelemetry/otlp-exporter-base": "0.215.0", - "@opentelemetry/propagator-b3": "2.7.0", - "@opentelemetry/propagator-jaeger": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/sdk-logs": "0.215.0", - "@opentelemetry/sdk-metrics": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0", - "@opentelemetry/sdk-trace-node": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", - "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.0", - "@opentelemetry/resources": "2.7.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.0.tgz", - "integrity": "sha512-RrFHOXw0IYp/OThew6QORdybnnLitUAUMCJKcQNBYS0hDkCYarO2vTkVxfrGxCIqd5XHSMvbCpBd/T8ZMw8oSg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "2.7.0", - "@opentelemetry/core": "2.7.0", - "@opentelemetry/sdk-trace-base": "2.7.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", - "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, "node_modules/@smithy/chunked-blob-reader": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", @@ -2852,15 +2178,6 @@ "node": ">= 10" } }, - "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.19.0" - } - }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -2870,27 +2187,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -3290,12 +2586,6 @@ "node": ">=10" } }, - "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "license": "MIT" - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -3318,70 +2608,6 @@ "node": ">=8" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3878,15 +3104,6 @@ "@esbuild/win32-x64": "0.28.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -4130,12 +3347,6 @@ "node": ">= 0.12" } }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" - }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -4294,15 +3505,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -4487,21 +3689,6 @@ "dev": true, "license": "MIT" }, - "node_modules/import-in-the-middle": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.1.tgz", - "integrity": "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.15.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^2.2.0", - "module-details-from-path": "^1.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4761,12 +3948,6 @@ "uc.micro": "^2.0.0" } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, "node_modules/log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -4799,12 +3980,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -5104,12 +4279,6 @@ "node": ">=10" } }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -5587,30 +4756,6 @@ "node": ">=10" } }, - "node_modules/protobufjs": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz", - "integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "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": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -6000,28 +5145,6 @@ "uuid": "bin/uuid" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", - "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3" - }, - "engines": { - "node": ">=9.3.0 || >=8.10.0 <9.0.0" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -6824,12 +5947,6 @@ "node": ">=0.8.0" } }, - "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", - "license": "MIT" - }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -7062,6 +6179,7 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -7073,42 +6191,6 @@ "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", diff --git a/package.json b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "type": "module", "scripts": { - "build": "npx spago build && npx esbuild output/Main/index.js --bundle --platform=node --format=esm --footer:js='main();' --outfile=server.js --external:http --external:https --external:dotenv --external:url --external:duckdb --external:@aws-sdk/client-s3 --external:prom-client --external:@opentelemetry/* && elm make src/Client.elm --output=client.js", - "release": "npx spago build && npx purs-backend-es build && npx esbuild output-es/Main/index.js --bundle --platform=node --format=esm --footer:js='main();' --outfile=server.js --external:http --external:https --external:dotenv --external:url --external:duckdb --external:@aws-sdk/client-s3 --external:prom-client --external:@opentelemetry/* && elm make src/Client.elm --optimize --output=client.js && npx uglifyjs client.js --compress \"pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe\" | npx uglifyjs --mangle --output client.js", + "build": "npx spago build && npx esbuild output/Main/index.js --bundle --platform=node --format=esm --footer:js='main();' --outfile=server.js --external:http --external:https --external:dotenv --external:url --external:duckdb --external:@aws-sdk/client-s3 --external:prom-client && elm make src/Client.elm --output=client.js", + "release": "npx spago build && npx purs-backend-es build && npx esbuild output-es/Main/index.js --bundle --platform=node --format=esm --footer:js='main();' --outfile=server.js --external:http --external:https --external:dotenv --external:url --external:duckdb --external:@aws-sdk/client-s3 --external:prom-client && elm make src/Client.elm --optimize --output=client.js && npx uglifyjs client.js --compress \"pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe\" | npx uglifyjs --mangle --output client.js", "test": "npx spago test", "tidy": "purs-tidy format-in-place src/**/*.purs" }, @@ -23,11 +23,6 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.1029.0", - "@opentelemetry/api": "^1.9.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.215.0", - "@opentelemetry/instrumentation-http": "^0.215.0", - "@opentelemetry/instrumentation-undici": "^0.25.0", - "@opentelemetry/sdk-node": "^0.215.0", "dotenv": "^17.4.1", "duckdb": "^1.4.4", "prom-client": "^15.1.3" diff --git a/src/Main.purs b/src/Main.purs @@ -357,7 +357,7 @@ handleRequest metricsEnabled contexts req res = do "/cover" -> withUser url \ctx -> serveCover ctx.config ctx.slug url res "/similar" -> - withUser url \ctx -> serveSimilar ctx.config url res + withUser url \ctx -> serveSimilar ctx.slug ctx.config url res "/healthz" -> withUser url \ctx -> serveHealthz ctx.conn res _ -> @@ -660,11 +660,12 @@ serveError res statusCode statusName message = do void $ writeString w UTF8 (statusName <> ": " <> message) end w -fetchCosineSimilar :: UserConfig -> String -> Aff String -fetchCosineSimilar cfg query = do +fetchCosineSimilar :: String -> UserConfig -> String -> Aff String +fetchCosineSimilar slug cfg query = do let apiKey = fromMaybe "" cfg.cosineApiKey if apiKey == "" then do Log.warn "cosineApiKey not configured for similar tracks" + liftEffect $ Metrics.incCosineRequest slug "not_configured" pure "{\"data\":{\"similar_tracks\":[]},\"success\":true}" else do let headers = { "User-Agent": "corpus/1.0 +https://codeberg.org/mtmn/corpus", "Authorization": "Bearer " <> apiKey } @@ -672,14 +673,17 @@ fetchCosineSimilar cfg query = do Log.info $ "Cosine Club: searching for: " <> query searchResult <- try $ fetch searchUrl { method: GET, headers: headers } case searchResult of - Left err -> + Left err -> do + liftEffect $ Metrics.incCosineRequest slug "error" throwError err Right searchRes -> if searchRes.status == 429 then do Log.warn "Cosine Club: rate limited on search" + liftEffect $ Metrics.incCosineRequest slug "rate_limited" throwError (Exception.error "Rate limit exceeded") else if searchRes.status /= 200 then do Log.warn $ "Cosine Club: search returned " <> show searchRes.status + liftEffect $ Metrics.incCosineRequest slug "error" throwError (Exception.error "Search API error") else do searchBody <- searchRes.text @@ -694,22 +698,27 @@ fetchCosineSimilar cfg query = do case mTrackId of Nothing -> do Log.info $ "Cosine Club: track not indexed: " <> query + liftEffect $ Metrics.incCosineRequest slug "not_indexed" pure "{\"data\":{\"similar_tracks\":[]},\"success\":true}" Just trackId -> do let similarUrl = "https://cosine.club/api/v1/tracks/" <> trackId <> "/similar?limit=10" Log.info $ "Cosine Club: fetching similar for ID " <> trackId similarResult <- try $ fetch similarUrl { method: GET, headers: headers } case similarResult of - Left err -> + Left err -> do + liftEffect $ Metrics.incCosineRequest slug "error" throwError err Right similarRes -> if similarRes.status == 429 then do Log.warn "Cosine Club: rate limited on similar" + liftEffect $ Metrics.incCosineRequest slug "rate_limited" throwError (Exception.error "Rate limit exceeded") else if similarRes.status /= 200 then do Log.warn $ "Cosine Club: similar API returned " <> show similarRes.status + liftEffect $ Metrics.incCosineRequest slug "error" throwError (Exception.error "Similar API error") - else + else do + liftEffect $ Metrics.incCosineRequest slug "success" similarRes.text serveStats :: Connection -> URL -> Response -> Effect Unit @@ -729,8 +738,8 @@ serveStats db url res = do void $ writeString w UTF8 responseBody end w -serveSimilar :: UserConfig -> URL -> Response -> Effect Unit -serveSimilar cfg url res = do +serveSimilar :: String -> UserConfig -> URL -> Response -> Effect Unit +serveSimilar slug cfg url res = do setHeader "Content-Type" "application/json" (toOutgoingMessage res) launchAff_ do let artist = fromMaybe "" (getQueryParam "artist" url) @@ -739,7 +748,7 @@ serveSimilar cfg url res = do liftEffect $ serveBadRequest res "Artist and track parameters are required" else do let query = artist <> " - " <> track - result <- try $ fetchCosineSimilar cfg query + result <- try $ fetchCosineSimilar slug cfg query case result of Left err -> do diff --git a/src/Metrics.js b/src/Metrics.js @@ -5,17 +5,6 @@ import { Histogram, collectDefaultMetrics, } from "prom-client"; -import { NodeSDK } from "@opentelemetry/sdk-node"; -import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; -import { HttpInstrumentation } from "@opentelemetry/instrumentation-http"; -import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici"; -import { - trace, - context, - propagation, - SpanKind, - SpanStatusCode, -} from "@opentelemetry/api"; // --------------------------------------------------------------------------- // Prometheus metrics — only active when METRICS_ENABLED=true @@ -82,6 +71,13 @@ const makeActiveMetrics = () => { registers: [registry], }); + const cosineRequestsTotal = new Counter({ + name: "corpus_cosine_requests_total", + help: "Total Cosine Club API requests by user and result", + labelNames: ["user", "result"], + registers: [registry], + }); + const dbBackupRunsTotal = new Counter({ name: "corpus_db_backup_runs_total", help: "Total database backup runs", @@ -109,6 +105,7 @@ const makeActiveMetrics = () => { incEnrichmentFetch: (user, source, result) => enrichmentFetchesTotal.inc({ user, source, result }), setEnrichmentQueueSize: (user, type, size) => enrichmentQueueSize.set({ user, type }, size), incCoverRequest: (user, source, result) => coverRequestsTotal.inc({ user, source, result }), + incCosineRequest: (user, result) => cosineRequestsTotal.inc({ user, result }), incDbBackupRun: (user, result) => dbBackupRunsTotal.inc({ user, result }), setDbBackupLastSuccess: (user) => dbBackupLastSuccessSeconds.setToCurrentTime({ user }), }; @@ -126,6 +123,7 @@ const makeNoOpMetrics = () => ({ incEnrichmentFetch: noOp, setEnrichmentQueueSize: noOp, incCoverRequest: noOp, + incCosineRequest: noOp, incDbBackupRun: noOp, setDbBackupLastSuccess: noOp, }); @@ -134,58 +132,6 @@ const activeMetrics = process.env.METRICS_ENABLED === "true" ? makeActiveMetrics() : makeNoOpMetrics(); // --------------------------------------------------------------------------- -// OpenTelemetry — only active when OTEL_EXPORTER_OTLP_ENDPOINT is set -// --------------------------------------------------------------------------- - -const makeTracer = () => { - if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return null; - const sdk = new NodeSDK({ - traceExporter: new OTLPTraceExporter(), - instrumentations: [ - new HttpInstrumentation({ - // Incoming requests are traced manually via wrapRequest below - ignoreIncomingRequestHook: () => true, - }), - new UndiciInstrumentation(), - ], - }); - sdk.start(); - const tracer = trace.getTracer( - process.env.OTEL_SERVICE_NAME || "corpus", - process.env.npm_package_version, - ); - process.on("SIGTERM", () => sdk.shutdown().finally(() => process.exit(0))); - return tracer; -}; - -const tracer = makeTracer(); - -const startHttpSpan = (method, path, headers) => { - if (!tracer) return null; - const parentCtx = propagation.extract(context.active(), headers); - return tracer.startSpan( - `${method} ${path}`, - { - kind: SpanKind.SERVER, - attributes: { - "http.method": method, - "http.target": path, - }, - }, - parentCtx, - ); -}; - -const endHttpSpan = (span, statusCode) => { - if (!span) return; - span.setAttribute("http.status_code", statusCode); - span.setStatus({ - code: statusCode >= 500 ? SpanStatusCode.ERROR : SpanStatusCode.OK, - }); - span.end(); -}; - -// --------------------------------------------------------------------------- // Exports // --------------------------------------------------------------------------- @@ -198,28 +144,18 @@ export const getMetricsImpl = (onSuccess) => (onError) => () => { export const getContentType = () => activeMetrics.contentType; -// Runs `handler` inside the active context of a server span so that any -// outbound HTTP/fetch calls made during handling automatically become child -// spans. Attaches a 'finish' listener to record metrics and end the span. +// Attaches a 'finish' listener to record metrics and log the request. // logFn: String -> Effect Unit — structured logger called on completion. -// req: Node IncomingMessage — used to extract W3C trace-context headers. export const wrapRequest = - (method) => (path) => (logFn) => (req) => (res) => (handler) => () => { + (method) => (path) => (logFn) => (_req) => (res) => (handler) => () => { const startMs = Date.now(); - const span = startHttpSpan(method, path, req.headers || {}); res.once("finish", () => { const durationMs = Date.now() - startMs; const status = res.statusCode || 0; activeMetrics.recordHttpRequest(method, path, status, durationMs); - endHttpSpan(span, status); logFn(`${method} ${path} ${status} ${durationMs}ms`)(); }); - if (span) { - const ctx = trace.setSpan(context.active(), span); - context.with(ctx, () => handler()); - } else { - handler(); - } + handler(); }; export const incSyncRuns = (user) => (source) => (result) => () => { @@ -246,6 +182,10 @@ export const incCoverRequest = (user) => (source) => (result) => () => { activeMetrics.incCoverRequest(user, source, result); }; +export const incCosineRequest = (user) => (result) => () => { + activeMetrics.incCosineRequest(user, result); +}; + export const incDbBackupRun = (user) => (result) => () => { activeMetrics.incDbBackupRun(user, result); }; diff --git a/src/Metrics.purs b/src/Metrics.purs @@ -14,10 +14,7 @@ foreign import getMetricsImpl :: (String -> Effect Unit) -> (String -> Effect Un -- | The MIME type to use for the /metrics response body. foreign import getContentType :: Effect String --- | Runs the given `Effect Unit` handler inside the active context of a server --- | span, so outbound HTTP/fetch calls become child spans automatically. --- | Attaches a 'finish' listener to record metrics and end the span. --- | W3C trace-context headers are extracted from `req` for span parenting. +-- | Attaches a 'finish' listener to record metrics and log the request. foreign import wrapRequest :: String -> String -> (String -> Effect Unit) -> IncomingMessage IMServer -> ServerResponse -> Effect Unit -> Effect Unit -- Sync @@ -32,6 +29,9 @@ foreign import setEnrichmentQueueSize :: String -> String -> Int -> Effect Unit -- Cover art foreign import incCoverRequest :: String -> String -> String -> Effect Unit +-- Cosine Club +foreign import incCosineRequest :: String -> String -> Effect Unit + -- Database backup foreign import incDbBackupRun :: String -> String -> Effect Unit foreign import setDbBackupLastSuccess :: String -> Effect Unit