From 8195df6bd6db774491bc8e8347ccf04b785373af Mon Sep 17 00:00:00 2001 From: Biki Das <36537203+bikidsx@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:37:28 +0530 Subject: [PATCH] Initial project setup with core files and examples Add core Rust implementation, OpenTelemetry proto files, API and storage modules, and embedded web UI. Include CI/CD GitHub workflows, Docker support, and example apps for Node.js (Express) and Python (FastAPI) demonstrating integration. Project is designed for local-first observability with zero config and in-memory storage. --- .dockerignore | 5 + .github/workflows/ci.yml | 44 ++ .github/workflows/release.yml | 106 +++ .gitignore | 23 + Cargo.toml | 42 ++ Dockerfile | 11 + README.md | 140 ++++ docker-compose.yml | 8 + examples/node-express/README.md | 42 ++ examples/node-express/app.js | 147 ++++ examples/node-express/bun.lock | 608 ++++++++++++++++ examples/node-express/package.json | 19 + examples/node-express/tracing.js | 43 ++ examples/python-fastapi/README.md | 42 ++ examples/python-fastapi/app.py | 111 +++ examples/python-fastapi/requirements.txt | 7 + idea.md | 274 +++++++ .../collector/logs/v1/logs_service.proto | 26 + .../collector/trace/v1/trace_service.proto | 26 + .../proto/common/v1/common.proto | 39 + proto/opentelemetry/proto/logs/v1/logs.proto | 67 ++ .../proto/resource/v1/resource.proto | 14 + .../opentelemetry/proto/trace/v1/trace.proto | 79 +++ src/api/handlers.rs | 94 +++ src/api/mod.rs | 14 + src/api/routes.rs | 29 + src/ingest/convert.rs | 214 ++++++ src/ingest/grpc.rs | 93 +++ src/ingest/mod.rs | 4 + src/main.rs | 43 ++ src/model/log.rs | 79 +++ src/model/mod.rs | 9 + src/model/service.rs | 27 + src/model/span.rs | 137 ++++ src/proto.rs | 19 + src/storage/mod.rs | 7 + src/storage/ring_buffer.rs | 123 ++++ src/storage/store.rs | 302 ++++++++ ui/dist/.gitkeep | 0 ui/dist/index.html | 668 ++++++++++++++++++ ui/fallback.html | 68 ++ 41 files changed, 3853 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 examples/node-express/README.md create mode 100644 examples/node-express/app.js create mode 100644 examples/node-express/bun.lock create mode 100644 examples/node-express/package.json create mode 100644 examples/node-express/tracing.js create mode 100644 examples/python-fastapi/README.md create mode 100644 examples/python-fastapi/app.py create mode 100644 examples/python-fastapi/requirements.txt create mode 100644 idea.md create mode 100644 proto/opentelemetry/proto/collector/logs/v1/logs_service.proto create mode 100644 proto/opentelemetry/proto/collector/trace/v1/trace_service.proto create mode 100644 proto/opentelemetry/proto/common/v1/common.proto create mode 100644 proto/opentelemetry/proto/logs/v1/logs.proto create mode 100644 proto/opentelemetry/proto/resource/v1/resource.proto create mode 100644 proto/opentelemetry/proto/trace/v1/trace.proto create mode 100644 src/api/handlers.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/routes.rs create mode 100644 src/ingest/convert.rs create mode 100644 src/ingest/grpc.rs create mode 100644 src/ingest/mod.rs create mode 100644 src/main.rs create mode 100644 src/model/log.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/service.rs create mode 100644 src/model/span.rs create mode 100644 src/proto.rs create mode 100644 src/storage/mod.rs create mode 100644 src/storage/ring_buffer.rs create mode 100644 src/storage/store.rs create mode 100644 ui/dist/.gitkeep create mode 100644 ui/dist/index.html create mode 100644 ui/fallback.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8829761 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +target/ +.git/ +*.md +!README.md +Cargo.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de97d69 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Check + run: cargo check + + - name: Test + run: cargo test + + - name: Build + run: cargo build --release + + # Test build on all platforms (without cross-compilation) + build-matrix: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build + run: cargo build --release + + - name: Test + run: cargo test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9264f3d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,106 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build: + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact: hydra-linux-x64 + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + artifact: hydra-linux-arm64 + cross: true + - os: macos-latest + target: x86_64-apple-darwin + artifact: hydra-darwin-x64 + - os: macos-latest + target: aarch64-apple-darwin + artifact: hydra-darwin-arm64 + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact: hydra-windows-x64.exe + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cross (Linux ARM64) + if: matrix.cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Build (cross) + if: matrix.cross + run: cross build --release --target ${{ matrix.target }} + + - name: Build (native) + if: ${{ !matrix.cross }} + run: cargo build --release --target ${{ matrix.target }} + + - name: Rename binary (Unix) + if: runner.os != 'Windows' + run: mv target/${{ matrix.target }}/release/hydra ${{ matrix.artifact }} + + - name: Rename binary (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: Move-Item target/${{ matrix.target }}/release/hydra.exe ${{ matrix.artifact }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }} + + release: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: List artifacts + run: find artifacts -type f + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: artifacts/**/* + generate_release_notes: true + + publish-npm: + needs: release + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Publish to npm + working-directory: npm + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac57595 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Rust +/target/ +Cargo.lock + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Examples - Python +examples/python-fastapi/__pycache__/ +examples/python-fastapi/*.pyc +examples/python-fastapi/.venv/ +examples/python-fastapi/venv/ + +# Examples - Node +examples/node-express/node_modules/ +examples/node-express/package-lock.json diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0b37ad0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "hydra" +version = "0.1.0" +edition = "2021" +description = "Local-first dev observability. Zero config. Just works." +license = "MIT" + +[dependencies] +# Async runtime +tokio = { version = "1", features = ["full"] } + +# Web server +axum = { version = "0.7", features = ["ws"] } +tower = "0.4" +tower-http = { version = "0.5", features = ["cors", "fs"] } + +# OTLP / OpenTelemetry (using pre-built proto crates) +tonic = "0.12" +prost = "0.13" +prost-types = "0.13" +opentelemetry-proto = { version = "0.27", features = ["gen-tonic", "logs", "trace"] } + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Utils +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1", features = ["v4", "serde"] } +parking_lot = "0.12" +dashmap = "6" +anyhow = "1" +hex = "0.4" + +# Embedded UI +rust-embed = { version = "8", features = ["axum"] } +mime_guess = "2" + +[build-dependencies] +# No build deps needed - using opentelemetry-proto crate diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..47bf47b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM rust:1.83-slim as builder + +WORKDIR /app +COPY . . +RUN cargo build --release + +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /app/target/release/hydra /usr/local/bin/hydra +EXPOSE 8080 4317 +CMD ["hydra"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9cf6a19 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +``` + ██╗ ██╗██╗ ██╗██████╗ ██████╗ █████╗ + ██║ ██║╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗ + ███████║ ╚████╔╝ ██║ ██║██████╔╝███████║ + ██╔══██║ ╚██╔╝ ██║ ██║██╔══██╗██╔══██║ + ██║ ██║ ██║ ██████╔╝██║ ██║██║ ██║ + ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ + + Local-first dev observability. + Zero config. Just works. +``` + +--- + +## What is this? + +You're running microservices locally. Something's slow. Something's broken. You add `console.log`. You restart. You squint at terminal output. You cry. + +**Hydra fixes this.** + +One command. Full observability. Traces, logs, the works. + +```bash +# No Rust? No problem. +npx hydra-dev + +# Or install globally +npm install -g hydra-dev +hydra-dev + +# Or with Docker +docker run -p 8080:8080 -p 4317:4317 ghcr.io/user/hydra + +# Or build from source +cargo run --release +``` + +Open http://localhost:8080. Done. + +--- + +## Quick Start + +**1. Point your app at it** +```bash +export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +``` + +**2. See everything** + +Open http://localhost:8080 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ HYDRA 12 traces · 47 logs │ +├─────────────────────────────────────────────────────────────┤ +│ [Traces] [Logs] [Services] [All Services ▼] │ +├─────────────────────────────────────────────────────────────┤ +│ 14:32:01 api-gateway POST /checkout 142ms │ +│ 14:32:00 payment-svc ProcessPayment 89ms │ +│ 14:31:58 api-gateway GET /users/123 12ms │ +│ 14:31:55 inventory CheckStock 8ms │ +└─────────────────────────────────────────────────────────────┘ +``` + +Click any trace → see the full waterfall with correlated logs. + +--- + +## Features + +- **Zero config** — just run it +- **Single binary** — no Kafka, no Kubernetes, no YAML files +- **Auto-correlation** — logs linked to traces automatically +- **Ephemeral** — data lives in memory, restart clears it (feature, not bug) +- **Fast** — sub-second startup, minimal resource usage + +--- + +## Endpoints + +| Port | Protocol | Purpose | +|------|----------|---------| +| `8080` | HTTP | Web UI + REST API | +| `4317` | gRPC | OTLP receiver | + +--- + +## API + +```bash +GET /api/traces/:id # Get trace with spans + logs +GET /api/spans # Recent spans +GET /api/logs # Recent logs +GET /api/services # Discovered services +GET /health # Health check +``` + +--- + +## Examples + +```bash +# Python FastAPI +cd examples/python-fastapi +pip install -r requirements.txt +python app.py + +# Node.js Express (auto-generates traffic) +cd examples/node-express +npm install && npm start +``` + +--- + +## Philosophy + +``` +Production has Datadog. +Local dev has println. + +This is the missing middle. +``` + +Hydra is **not** for production. It's for your laptop. It's for `docker-compose up`. It's for "why is this endpoint slow?" without deploying to staging. + +--- + +## Tech + +- Rust + Tokio +- OTLP over gRPC +- In-memory ring buffers +- Embedded web UI + +--- + +## License + +MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a4db1e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + hydra: + build: . + ports: + - "8080:8080" # Web UI + - "4317:4317" # OTLP gRPC + environment: + - RUST_LOG=info diff --git a/examples/node-express/README.md b/examples/node-express/README.md new file mode 100644 index 0000000..255fd84 --- /dev/null +++ b/examples/node-express/README.md @@ -0,0 +1,42 @@ +# Node.js Express Example + +An Express app instrumented with OpenTelemetry, sending traces and logs to Hydra. + +Auto-generates traffic on startup so you can see data immediately. + +## Setup + +```bash +# Install dependencies +npm install +# or +bun install + +# Start Hydra (from project root) +cargo run --release + +# Run the example app (auto-generates traffic) +npm start +# or +bun run start +``` + +## What Happens + +On startup, the app: +1. Starts an Express server on port 3000 +2. Automatically calls random endpoints every 3 seconds +3. Sends traces and logs to Hydra at localhost:4317 + +Open http://localhost:8080 to see the data flowing in. + +## Manual Testing + +```bash +curl http://localhost:3000/ +curl http://localhost:3000/users/123 +curl http://localhost:3000/slow +curl http://localhost:3000/error +curl http://localhost:3000/chain +curl http://localhost:3000/users/0 # 404 +``` diff --git a/examples/node-express/app.js b/examples/node-express/app.js new file mode 100644 index 0000000..7b5a26a --- /dev/null +++ b/examples/node-express/app.js @@ -0,0 +1,147 @@ +require('./tracing'); + +const express = require('express'); +const { trace, context } = require('@opentelemetry/api'); +const { logs } = require('@opentelemetry/api-logs'); + +const app = express(); +const tracer = trace.getTracer('demo-node-api'); +const logger = logs.getLogger('demo-node-api'); + +// Helper to log with trace context +function log(severity, message, attrs = {}) { + const span = trace.getActiveSpan(); + const spanContext = span?.spanContext(); + + logger.emit({ + severityNumber: severity, + severityText: getSeverityText(severity), + body: message, + attributes: attrs, + ...(spanContext && { + traceId: spanContext.traceId, + spanId: spanContext.spanId, + }), + }); + + console.log(`[${getSeverityText(severity)}] ${message}`); +} + +function getSeverityText(num) { + if (num <= 4) return 'TRACE'; + if (num <= 8) return 'DEBUG'; + if (num <= 12) return 'INFO'; + if (num <= 16) return 'WARN'; + if (num <= 20) return 'ERROR'; + return 'FATAL'; +} + +app.get('/', (req, res) => { + log(9, 'Root endpoint called'); + res.json({ message: 'Hello from demo-node-api' }); +}); + +app.get('/users/:id', (req, res) => { + log(9, `Fetching user ${req.params.id}`); + + const span = tracer.startSpan('fetch_user_from_db'); + span.setAttribute('db.system', 'mongodb'); + span.setAttribute('user.id', req.params.id); + + // Simulate DB call + setTimeout(() => { + if (req.params.id === '0') { + log(17, 'User not found', { user_id: req.params.id }); + span.setStatus({ code: 2, message: 'Not found' }); + span.end(); + res.status(404).json({ error: 'User not found' }); + } else { + log(9, `Found user ${req.params.id}`); + span.end(); + res.json({ id: req.params.id, name: `User ${req.params.id}` }); + } + }, Math.random() * 50); +}); + +app.get('/slow', (req, res) => { + log(13, 'Slow endpoint called - this will take a while'); + + const span = tracer.startSpan('slow_operation'); + + setTimeout(() => { + log(9, 'Slow operation completed'); + span.end(); + res.json({ status: 'done' }); + }, 500 + Math.random() * 1000); +}); + +app.get('/error', (req, res) => { + log(17, 'Intentional error triggered'); + + const span = tracer.startSpan('error_operation'); + span.recordException(new Error('Intentional error')); + span.setStatus({ code: 2, message: 'Error' }); + span.end(); + + res.status(500).json({ error: 'Something went wrong' }); +}); + +app.get('/chain', (req, res) => { + log(9, 'Starting chain operation'); + + const span1 = tracer.startSpan('step_1_validate'); + setTimeout(() => { + log(9, 'Validation complete'); + span1.end(); + + const span2 = tracer.startSpan('step_2_process'); + setTimeout(() => { + log(9, 'Processing complete'); + span2.end(); + + const span3 = tracer.startSpan('step_3_save'); + setTimeout(() => { + log(9, 'Save complete'); + span3.end(); + res.json({ status: 'chain complete' }); + }, 10 + Math.random() * 30); + }, 20 + Math.random() * 50); + }, 10 + Math.random() * 20); +}); + +const PORT = 3000; + +// Auto-generate traffic on startup +app.listen(PORT, () => { + console.log(`Demo Node API running on http://localhost:${PORT}`); + console.log('Auto-generating traffic to Hydra...\n'); + + // Generate initial traffic + generateTraffic(); + + // Keep generating traffic every 3 seconds + setInterval(generateTraffic, 3000); +}); + +async function generateTraffic() { + const endpoints = [ + 'http://localhost:3000/', + 'http://localhost:3000/users/1', + 'http://localhost:3000/users/42', + 'http://localhost:3000/users/0', // 404 + 'http://localhost:3000/chain', + 'http://localhost:3000/slow', + 'http://localhost:3000/error', // 500 + ]; + + // Pick 2-3 random endpoints + const count = 2 + Math.floor(Math.random() * 2); + for (let i = 0; i < count; i++) { + const url = endpoints[Math.floor(Math.random() * endpoints.length)]; + try { + await fetch(url); + } catch (e) { + // ignore + } + } +} diff --git a/examples/node-express/bun.lock b/examples/node-express/bun.lock new file mode 100644 index 0000000..aac0b20 --- /dev/null +++ b/examples/node-express/bun.lock @@ -0,0 +1,608 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "hydra-node-example", + "dependencies": { + "@opentelemetry/api": "^1.7.0", + "@opentelemetry/auto-instrumentations-node": "^0.40.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.46.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.46.0", + "@opentelemetry/sdk-node": "^0.46.0", + "express": "^4.18.2", + }, + }, + }, + "packages": { + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.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" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + + "@hapi/b64": ["@hapi/b64@5.0.0", "", { "dependencies": { "@hapi/hoek": "9.x.x" } }, "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw=="], + + "@hapi/boom": ["@hapi/boom@9.1.4", "", { "dependencies": { "@hapi/hoek": "9.x.x" } }, "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw=="], + + "@hapi/bourne": ["@hapi/bourne@2.1.0", "", {}, "sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q=="], + + "@hapi/catbox": ["@hapi/catbox@12.1.1", "", { "dependencies": { "@hapi/boom": "^10.0.1", "@hapi/hoek": "^11.0.2", "@hapi/podium": "^5.0.0", "@hapi/validate": "^2.0.1" } }, "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw=="], + + "@hapi/cryptiles": ["@hapi/cryptiles@5.1.0", "", { "dependencies": { "@hapi/boom": "9.x.x" } }, "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA=="], + + "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="], + + "@hapi/iron": ["@hapi/iron@6.0.0", "", { "dependencies": { "@hapi/b64": "5.x.x", "@hapi/boom": "9.x.x", "@hapi/bourne": "2.x.x", "@hapi/cryptiles": "5.x.x", "@hapi/hoek": "9.x.x" } }, "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw=="], + + "@hapi/podium": ["@hapi/podium@4.1.3", "", { "dependencies": { "@hapi/hoek": "9.x.x", "@hapi/teamwork": "5.x.x", "@hapi/validate": "1.x.x" } }, "sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g=="], + + "@hapi/shot": ["@hapi/shot@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2", "@hapi/validate": "^2.0.1" } }, "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw=="], + + "@hapi/teamwork": ["@hapi/teamwork@5.1.1", "", {}, "sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg=="], + + "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="], + + "@hapi/validate": ["@hapi/validate@1.1.3", "", { "dependencies": { "@hapi/hoek": "^9.0.0", "@hapi/topo": "^5.0.0" } }, "sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA=="], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.46.0", "", { "dependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-+9BcqfiEDGPXEIo+o3tso/aqGM5dGbGwAkGVp3FPpZ8GlkK1YlaKRd9gMVyPaeRATwvO5wYGGnCsAc/sMMM9Qw=="], + + "@opentelemetry/auto-instrumentations-node": ["@opentelemetry/auto-instrumentations-node@0.40.3", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/instrumentation-amqplib": "^0.33.5", "@opentelemetry/instrumentation-aws-lambda": "^0.37.4", "@opentelemetry/instrumentation-aws-sdk": "^0.37.2", "@opentelemetry/instrumentation-bunyan": "^0.34.1", "@opentelemetry/instrumentation-cassandra-driver": "^0.34.2", "@opentelemetry/instrumentation-connect": "^0.32.4", "@opentelemetry/instrumentation-cucumber": "^0.2.1", "@opentelemetry/instrumentation-dataloader": "^0.5.4", "@opentelemetry/instrumentation-dns": "^0.32.5", "@opentelemetry/instrumentation-express": "^0.34.1", "@opentelemetry/instrumentation-fastify": "^0.32.6", "@opentelemetry/instrumentation-fs": "^0.8.4", "@opentelemetry/instrumentation-generic-pool": "^0.32.5", "@opentelemetry/instrumentation-graphql": "^0.36.1", "@opentelemetry/instrumentation-grpc": "^0.46.0", "@opentelemetry/instrumentation-hapi": "^0.33.3", "@opentelemetry/instrumentation-http": "^0.46.0", "@opentelemetry/instrumentation-ioredis": "^0.36.1", "@opentelemetry/instrumentation-knex": "^0.32.4", "@opentelemetry/instrumentation-koa": "^0.36.4", "@opentelemetry/instrumentation-lru-memoizer": "^0.33.5", "@opentelemetry/instrumentation-memcached": "^0.32.5", "@opentelemetry/instrumentation-mongodb": "^0.38.1", "@opentelemetry/instrumentation-mongoose": "^0.34.0", "@opentelemetry/instrumentation-mysql": "^0.34.5", "@opentelemetry/instrumentation-mysql2": "^0.34.5", "@opentelemetry/instrumentation-nestjs-core": "^0.33.4", "@opentelemetry/instrumentation-net": "^0.32.5", "@opentelemetry/instrumentation-pg": "^0.37.2", "@opentelemetry/instrumentation-pino": "^0.34.5", "@opentelemetry/instrumentation-redis": "^0.35.5", "@opentelemetry/instrumentation-redis-4": "^0.35.6", "@opentelemetry/instrumentation-restify": "^0.34.3", "@opentelemetry/instrumentation-router": "^0.33.4", "@opentelemetry/instrumentation-socket.io": "^0.35.0", "@opentelemetry/instrumentation-tedious": "^0.6.5", "@opentelemetry/instrumentation-winston": "^0.33.1", "@opentelemetry/resource-detector-alibaba-cloud": "^0.28.5", "@opentelemetry/resource-detector-aws": "^1.3.5", "@opentelemetry/resource-detector-container": "^0.3.5", "@opentelemetry/resource-detector-gcp": "^0.29.5", "@opentelemetry/resources": "^1.12.0", "@opentelemetry/sdk-node": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.4.1" } }, "sha512-MgBCzpFU4FBQEsXPgt5driYzxErf2JGntQ8Amtc94sqrnvq+FKdEBa6vxOpZlPM+c4Xr6tPpAT1ecTBV8U87hw=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.19.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-0i1ECOc9daKK3rjUgDDXf0GDD5XfCou5lXnt2DALIc2qKoruPPcesobNKE54laSVUWnC3jX26RzuOa31g0V32A=="], + + "@opentelemetry/core": ["@opentelemetry/core@1.19.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-w42AukJh3TP8R0IZZOVJVM/kMWu8g+lm4LzT70WtuKqhwq7KVhcDzZZuZinWZa6TtQCl7Smt2wolEYzpHabOgw=="], + + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.46.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-grpc-exporter-base": "0.46.0", "@opentelemetry/otlp-transformer": "0.46.0", "@opentelemetry/sdk-logs": "0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-s1pTSuuO3VkSuWsWzFNepNH5NkDF+RGn94ltPKgVr32avgUVhSH9SG8m42Cus/lXDCskbanDs/5eA0d/Ujnfxg=="], + + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.46.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-grpc-exporter-base": "0.46.0", "@opentelemetry/otlp-transformer": "0.46.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-kR4kehnfIhv7v/2MuNYfrlh9A/ZtQofwCzurTIplornUjdzhKDGgjui1NkNTqTfM1QkqfCiavGsf5hwocx29bA=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-exporter-base": "0.46.0", "@opentelemetry/otlp-transformer": "0.46.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-vZ2pYOB+qrQ+jnKPY6Gnd58y1k/Ti//Ny6/XsSX7/jED0X77crtSVgC6N5UA0JiGJOh6QB2KE9gaH99010XHzg=="], + + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-exporter-base": "0.46.0", "@opentelemetry/otlp-proto-exporter-base": "0.46.0", "@opentelemetry/otlp-transformer": "0.46.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-A7PftDM57w1TLiirrhi8ceAnCpYkpUBObELdn239IyYF67zwngImGfBLf5Yo3TTAOA2Oj1TL76L8zWVL8W+Suw=="], + + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0", "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-TY1fy4JiOBN5a8T9fknqTMcz0DXIeFBr6sklaLCgwtj+G699a5R4CekNwpeM7DHSwC44UMX7gljO2I6dYsTS3A=="], + + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.46.0", "", { "dependencies": { "@types/shimmer": "^1.0.2", "import-in-the-middle": "1.7.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw=="], + + "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.33.5", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-WQ/XPzNLOHL3fpsmgoQUkiKCkJ09hvPN8wGrGzzOHMiJ5/3LqvfvxsJ4Rcd6aWkA4il3hEfpl+V0VF0t/DP65A=="], + + "@opentelemetry/instrumentation-aws-lambda": ["@opentelemetry/instrumentation-aws-lambda@0.37.4", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/propagator-aws-xray": "^1.3.1", "@opentelemetry/resources": "^1.8.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/aws-lambda": "8.10.122" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/wdZwUalIWAbxeycvmE+25c1xCMhe5EUuj8bN0MWWN3L8N2SYvfv6DmiRgwrTIPXRgIyFugh2udNiF4MezZN4Q=="], + + "@opentelemetry/instrumentation-aws-sdk": ["@opentelemetry/instrumentation-aws-sdk@0.37.2", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/propagation-utils": "^0.30.5", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eFHHOk0P9EFQ9Ho+2w7KH9R8cH7hut0/ePSsrk0nAM6Tiq2lBPeHn8ialCWESAA9jSjy+iuDelwmqAEXEe+jrg=="], + + "@opentelemetry/instrumentation-bunyan": ["@opentelemetry/instrumentation-bunyan@0.34.1", "", { "dependencies": { "@opentelemetry/api-logs": "^0.46.0", "@opentelemetry/instrumentation": "^0.46.0", "@types/bunyan": "1.8.9" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+eshbCFr2dkUYO2jCpbYGFC5hs94UCOsQRK1XqNOjeiNvQRtqvKYqk8ARwJBYBX+aW4J02jOliAHQUh/d7gYPg=="], + + "@opentelemetry/instrumentation-cassandra-driver": ["@opentelemetry/instrumentation-cassandra-driver@0.34.2", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-wuzq7QQ9o7PJnzseblNfBcURtM+9AwO6e1m644QYtAb/6YRR6qg6gAmAipVeQu01H5BuHBFC/92svaAkdIV2WQ=="], + + "@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.32.4", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/connect": "3.4.36" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-oUGph9idncdnHlQMwdIs6m6mii7Kdp9PpHkM9roOZy71h+2vvf6+cVn45bs2unBbE2Vxho2i/049QQbaYKDYlw=="], + + "@opentelemetry/instrumentation-cucumber": ["@opentelemetry/instrumentation-cucumber@0.2.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-ydF0DpmE0D6wccAbxx1F+6kokzcSSRy3X78Bvgok/3fHUSSshGLErqNiQL1HV44OIcV6392P3tu/jtXtUq3UDQ=="], + + "@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.5.4", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-l1qQvvygxZJw+S+4hgYgzvT4GArqBrar42wzB5LVsOy04+gmbDw/4y7IqxZYepFyXKuBownGS8pR4huRL/Tj/A=="], + + "@opentelemetry/instrumentation-dns": ["@opentelemetry/instrumentation-dns@0.32.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "semver": "^7.5.4" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fHJqrbezpZSipo4O9nF9yGq6R8oyr1W2gSlyk1foJNXBaqdCODTlzIa7BP50vGtLBN/m+qO8pMOWCJmYSBX35g=="], + + "@opentelemetry/instrumentation-express": ["@opentelemetry/instrumentation-express@0.34.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pQBQxXpkH6imvzwCdPcw2FKjB1cphoRpmWTiGi6vtBdKXCP0hpne613ycpwhGG7C17S+mbarVmukbJKR4rmh6Q=="], + + "@opentelemetry/instrumentation-fastify": ["@opentelemetry/instrumentation-fastify@0.32.6", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-UkBu8rAqeVC034jsRMiEAmYhFQ03pvmE/MnoPKE9gAbgVtPILdekHYqAKM0MdqnEjW7pO45t4wWsbtIcN0eiBw=="], + + "@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.8.4", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-g99963nK8TuisUM3KH+ee5hOCHdCHSKiAdmy0RMdiKT7ITh3rXUct7fghQibViQA7FVPkdwM9+uRKkigJSFS9w=="], + + "@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.32.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MNFloXa3SDg/rHh397JnWADr7iYQ6HHRwT7ZId5Vt0L1Xx+Qp3XSIILsXk5qTXVUIytEIVgn8iMT+kZILHzSqg=="], + + "@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.36.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-EOvaS+d2909TOJJjNTsb0vHaLg8WdTLoQS8bRKdy3lMgdd7I4OL9/LSC7dp4M8CvJKz9B464Ix9PnARvhMkNOw=="], + + "@opentelemetry/instrumentation-grpc": ["@opentelemetry/instrumentation-grpc@0.46.0", "", { "dependencies": { "@opentelemetry/instrumentation": "0.46.0", "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KemIpB4jmywQv/+MbVoUIMVp3vr+rzra37TYbN7kTsbrn213YlzdXVamf6nq/yChI6q+9JlUnCdSZf86D6NO6g=="], + + "@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.33.3", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/hapi__hapi": "20.0.13" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-l14u1TFPXMUjfHqnrHtzuMyLq6V8BikwhYJO/hIgkbaPCxS38TloCtDLJvcs8S8AZlcQfkUqE/NFgXYETZRo+Q=="], + + "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/instrumentation": "0.46.0", "@opentelemetry/semantic-conventions": "1.19.0", "semver": "^7.5.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-t5cxgqfV9AcxVP00/OL1ggkOSZM57VXDpvlWaOidYyyfLKcUJ9e2fGbNwoVsGFboRDeH0iFo7gLA3EEvX13wCA=="], + + "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.36.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/ioredis4": "npm:@types/ioredis@^4.28.10" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-xxab7n4TD5igCFR5N0j5PJFYVeOXm54ZtCARVS32xavwGyGf+Sb6VtuVCLdl0re4JENCg18FO97Dyb1ql2EBUA=="], + + "@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.32.4", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-vqxK1wqhktQnBA7nGr6nqfhV5irSXK9v0R29hqlyTr/cB/86Hn3Z98zH58QvHo131FcE+d70QdiXu3P9S5vq4g=="], + + "@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.36.4", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/koa": "2.13.9", "@types/koa__router": "12.0.3" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Qt6IF0mo3FZZ+x4NQhv/pViDtPSw/h/QYDvyIqMCuqUibqta619WLUCwAcanKnZWvzMuYh6lT0TnZ8ktjXo6jQ=="], + + "@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.33.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-vbm9QPEYD3FZNGSa+7xQ7uOkgbJtskIwp1KnsCpmdBBiulhBaqqgeNLFo7gd8UxMrI2Vu3LTlZil2D0dVt8g/A=="], + + "@opentelemetry/instrumentation-memcached": ["@opentelemetry/instrumentation-memcached@0.32.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/memcached": "^2.2.6" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-1MS9LfgZruAsWOHVDTI+/Wy9mFFivUlpGUwYC4D+ho1I4NUyE33XHwL1BKcjMupkuRnE0JmbhdBfTG9Ai1vFkw=="], + + "@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.38.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/sdk-metrics": "^1.9.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-X6YjE8dOCf8lG8FGmoAvczZq7LtgYaRzZcLGthZSUJQ2rfp1JJRlJixc+COvhrn1HJj5ab+AsSdUQgTpfQgEHQ=="], + + "@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.34.0", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-/wGNK7qR/QXpcidXntKsnfACHETp8G/f2o/k8FPvJ2lukvdM9r7cHLys8QwkJkTN8Kt3qG1VIwarGKYtE/zOSw=="], + + "@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.34.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/mysql": "2.15.22" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-cE8z1uJTeLcMj+R31t1pLkLqt3ryGMl1HApxsqqf8YCSHetrkVwGZOcyQ3phfgGSaNlC4/pdf3CQqfjhXbLWlA=="], + + "@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.34.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Ai3+cJ83b11kLypIVEPLHuYCiIQjf828hHJPpllr78EhmHiq4S1yTW34aP3BzCBY+5Adr5PS5Nnv7NLBI/YfaQ=="], + + "@opentelemetry/instrumentation-nestjs-core": ["@opentelemetry/instrumentation-nestjs-core@0.33.4", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AynD6TesE0bZg9UzS4ZLG//6TmU8GkRrjubhxs7jYZ3JRAlJAq1In0zSwM082JOIs7wr286nhs1FmqK91cG1cg=="], + + "@opentelemetry/instrumentation-net": ["@opentelemetry/instrumentation-net@0.32.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-omBLTXyJeCtBy1MzVi+IzUcpXiup90k0z3AGhNKbzro4RhpsdMpN9O+3zZCXCIHMuyifhy7z8w99wmvvFO/FCQ=="], + + "@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.37.2", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@opentelemetry/sql-common": "^0.40.0", "@types/pg": "8.6.1", "@types/pg-pool": "2.0.4" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MAiKqdtGItYjvD6rOCyGS27CdMaDnh2JuImIHXhrPjq/sb2JlBNm6m1e4BH4uik1VfcKt/I3pI3UkydSWIscCg=="], + + "@opentelemetry/instrumentation-pino": ["@opentelemetry/instrumentation-pino@0.34.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-auYDSeFhkPFXayT/060mQRL7O88Bt5NKcV0qOfquNa9J5/qs5dlJYdTOraxPrjiGPM3tHaAJ+AvAhR0CPYgZew=="], + + "@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.35.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-UPYUncDlLqDPtyU11UhyZOUxAyPQS6yQGT0b96KjpqMhmuRb3b0WxzZh3SoIaAyprL5f9fxyeV2HfSulR0aWFQ=="], + + "@opentelemetry/instrumentation-redis-4": ["@opentelemetry/instrumentation-redis-4@0.35.6", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/redis-common": "^0.36.1", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OVSUJZAuy6OX18X2TKPdPlpwM5t4FooJU9QXiUxezhdMvfIAu00Agchw+gRbszkM7nvQ9dkXFOZO3nTmJNcLcA=="], + + "@opentelemetry/instrumentation-restify": ["@opentelemetry/instrumentation-restify@0.34.3", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nUqf4RTcQyc/YTWMT0e/ZqvuQqhRH04MOoSwaH6ocmyrEhKdPDq9AbvSMerQ/AxNC9yju/PytgzFFWH45hh3KA=="], + + "@opentelemetry/instrumentation-router": ["@opentelemetry/instrumentation-router@0.33.4", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4140BgILsL0H8tA0BBN2tjzajgjxlDqd4xPatk2i5q9ofy8wlB0BlDJ4m36U7G1z0v+a+LshQSNqPP2VHZ9ORw=="], + + "@opentelemetry/instrumentation-socket.io": ["@opentelemetry/instrumentation-socket.io@0.35.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ED1/Wco05H9fepKqX7n2kONq9EXxkBZTKS29nUH9vVL7wSIT8sBIDqpHz94CnQ8xuicaQQ7c5h9TVuhjtzV43Q=="], + + "@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.6.5", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/semantic-conventions": "^1.0.0", "@types/tedious": "^4.0.10" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-jjdrDegLUodz9np0yKCAa7sLxlAGTsxM7wU7l1mQ2NM6PHAGv4X3eSFClUk3fOipLx4+r5jTLnlsgu7g9CW+Qw=="], + + "@opentelemetry/instrumentation-winston": ["@opentelemetry/instrumentation-winston@0.33.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.46.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4tSORoAY9f+yzqNVGcGr/3GydPMfgSiKK1OESc+qBwVTz0bmz4cOrhCruCngGzoqDCmPYpwqwR/8j4wRKgcUpw=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-hfkh7cG17l77ZSLRAogz19SIJzr0KeC7xv5PDyTFbHFpwwoxV/bEViO49CqUFH6ckXB63NrltASP9R7po+ahTQ=="], + + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.46.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-exporter-base": "0.46.0", "protobufjs": "^7.2.3" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-/KB/xfZZiWIY2JknvCoT/e9paIzQO3QCBN5gR6RyxpXM/AGx3YTAOKvB/Ts9Va19jo5aE74gB7emhFaCNy4Rmw=="], + + "@opentelemetry/otlp-proto-exporter-base": ["@opentelemetry/otlp-proto-exporter-base@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/otlp-exporter-base": "0.46.0", "protobufjs": "^7.2.3" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-rEJBA8U2AxfEzrdIUcyyjOweyVFkO6V1XAxwP161JkxpvNuVDdULHAfRVnGtoZhiVA1XsJKcpIIq2MEKAqq4cg=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.46.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.46.0", "@opentelemetry/core": "1.19.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-logs": "0.46.0", "@opentelemetry/sdk-metrics": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, "sha512-Fj9hZwr6xuqgsaERn667Uf6kuDG884puWhyrai2Jen2Fq+bGf4/5BzEJp/8xvty0VSU4EfXOto/ys3KpSz2UHg=="], + + "@opentelemetry/propagation-utils": ["@opentelemetry/propagation-utils@0.30.16", "", { "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-ZVQ3Z/PQ+2GQlrBfbMMMT0U7MzvYZLCPP800+ooyaBqm4hMvuQHfP028gB9/db0mwkmyEAMad9houukUVxhwcw=="], + + "@opentelemetry/propagator-aws-xray": ["@opentelemetry/propagator-aws-xray@1.26.2", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-k43wxTjKYvwfce9L4eT8fFYy/ATmCfPHZPZsyT/6ABimf2KE1HafoOsIcxLOtmNSZt6dCvBIYCrXaOWta20xJg=="], + + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-v7y5IBOKBm0vP3yf0DHzlw4L2gL6tZ0KeeMTaxfO5IuomMffDbrGWcvYFp0Dt4LdZctTSK523rVLBB9FBHBciQ=="], + + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-dedkOoTzKg+nYoLWCMp0Im+wo+XkTRW6aXhi8VQRtMW/9SNJGOllCJSu8llToLxMDF0+6zu7OCrKkevAof2tew=="], + + "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.36.2", "", {}, "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g=="], + + "@opentelemetry/resource-detector-alibaba-cloud": ["@opentelemetry/resource-detector-alibaba-cloud@0.28.10", "", { "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.22.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-TZv/1Y2QCL6sJ+X9SsPPBXe4786bc/Qsw0hQXFsNTbJzDTGGUmOAlSZ2qPiuqAd4ZheUYfD+QA20IvAjUz9Hhg=="], + + "@opentelemetry/resource-detector-aws": ["@opentelemetry/resource-detector-aws@1.12.0", "", { "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.10.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-Cvi7ckOqiiuWlHBdA1IjS0ufr3sltex2Uws2RK6loVp4gzIJyOijsddAI6IZ5kiO8h/LgCWe8gxPmwkTKImd+Q=="], + + "@opentelemetry/resource-detector-container": ["@opentelemetry/resource-detector-container@0.3.11", "", { "dependencies": { "@opentelemetry/resources": "^1.0.0", "@opentelemetry/semantic-conventions": "^1.22.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-22ndMDakxX+nuhAYwqsciexV8/w26JozRUV0FN9kJiqSWtA1b5dCVtlp3J6JivG5t8kDN9UF5efatNnVbqRT9Q=="], + + "@opentelemetry/resource-detector-gcp": ["@opentelemetry/resource-detector-gcp@0.29.13", "", { "dependencies": { "@opentelemetry/core": "^1.0.0", "@opentelemetry/resources": "^1.10.0", "@opentelemetry/semantic-conventions": "^1.27.0", "gcp-metadata": "^6.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-vdotx+l3Q+89PeyXMgKEGnZ/CwzwMtuMi/ddgD9/5tKZ08DfDGB2Npz9m2oXPHRCjc4Ro6ifMqFlRyzIvgOjhg=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-RgxvKuuMOf7nctOeOvpDjt2BpZvZGr9Y0vf7eGtY5XYZPkh2p7e2qub1S2IArdBMf9kEbz0SfycqCviOu9isqg=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.46.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/resources": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.8.0", "@opentelemetry/api-logs": ">=0.39.1" } }, "sha512-Knlyk4+G72uEzNh6GRN1Fhmrj+/rkATI5/lOrevN7zRDLgp4kfyZBGGoWk7w+qQjlYvwhIIdPVxlIcipivdZIg=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/resources": "1.19.0", "lodash.merge": "^4.6.2" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, "sha512-FiMii40zr0Fmys4F1i8gmuCvbinBnBsDeGBr4FQemOf0iPCLytYQm5AZJ/nn4xSc71IgKBQwTFQRAGJI7JvZ4Q=="], + + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.46.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.46.0", "@opentelemetry/core": "1.19.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.46.0", "@opentelemetry/exporter-trace-otlp-http": "0.46.0", "@opentelemetry/exporter-trace-otlp-proto": "0.46.0", "@opentelemetry/exporter-zipkin": "1.19.0", "@opentelemetry/instrumentation": "0.46.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/sdk-logs": "0.46.0", "@opentelemetry/sdk-metrics": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0", "@opentelemetry/sdk-trace-node": "1.19.0", "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.8.0" } }, "sha512-BQhzdCRZXchhKjZaFkgxlgoowjOt/QXekJ1CZgfvFO9Yg5GV15LyJFUEyQkDyD8XbshGo3Cnj0WZMBnDWtWY1A=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@1.19.0", "", { "dependencies": { "@opentelemetry/core": "1.19.0", "@opentelemetry/resources": "1.19.0", "@opentelemetry/semantic-conventions": "1.19.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-+IRvUm+huJn2KqfFW3yW/cjvRwJ8Q7FzYHoUNx5Fr0Lws0LxjMJG1uVB8HDpLwm7mg5XXH2M5MF+0jj5cM8BpQ=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@1.19.0", "", { "dependencies": { "@opentelemetry/context-async-hooks": "1.19.0", "@opentelemetry/core": "1.19.0", "@opentelemetry/propagator-b3": "1.19.0", "@opentelemetry/propagator-jaeger": "1.19.0", "@opentelemetry/sdk-trace-base": "1.19.0", "semver": "^7.5.2" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.8.0" } }, "sha512-TCiEq/cUjM15RFqBRwWomTVbOqzndWL4ILa7ZCu0zbjU1/XY6AgHkgrgAc7vGP6TjRqH4Xryuglol8tcIfbBUQ=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.19.0", "", {}, "sha512-14jRpC8f5c0gPSwoZ7SbEJni1PqI+AhAE8m1bMz6v+RPM4OlP1PT2UHBJj5Qh/ALLPjhVU/aZUK3YyjTUqqQVg=="], + + "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.40.1", "", { "dependencies": { "@opentelemetry/core": "^1.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + + "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="], + + "@sideway/formula": ["@sideway/formula@3.0.1", "", {}, "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="], + + "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="], + + "@types/accepts": ["@types/accepts@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ=="], + + "@types/aws-lambda": ["@types/aws-lambda@8.10.122", "", {}, "sha512-vBkIh9AY22kVOCEKo5CJlyCgmSWvasC+SWUxL/x/vOwRobMpI/HG1xp/Ae3AqmSiZeLUbOhW0FCD3ZjqqUxmXw=="], + + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + + "@types/bunyan": ["@types/bunyan@1.8.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw=="], + + "@types/connect": ["@types/connect@3.4.36", "", { "dependencies": { "@types/node": "*" } }, "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w=="], + + "@types/content-disposition": ["@types/content-disposition@0.5.9", "", {}, "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ=="], + + "@types/cookies": ["@types/cookies@0.9.2", "", { "dependencies": { "@types/connect": "*", "@types/express": "*", "@types/keygrip": "*", "@types/node": "*" } }, "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A=="], + + "@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="], + + "@types/hapi__catbox": ["@types/hapi__catbox@12.1.0", "", { "dependencies": { "@hapi/catbox": "*" } }, "sha512-v4XNUu6w9LmGgDZ4+OT1dSuvme5qNxFFyMVpqYPNugw4zN5xpWsNCsN7/0jG7wCKx39EQctW9iTT1O55UnH6/Q=="], + + "@types/hapi__hapi": ["@types/hapi__hapi@20.0.13", "", { "dependencies": { "@hapi/boom": "^9.0.0", "@hapi/iron": "^6.0.0", "@hapi/podium": "^4.1.3", "@types/hapi__catbox": "*", "@types/hapi__mimos": "*", "@types/hapi__shot": "*", "@types/node": "*", "joi": "^17.3.0" } }, "sha512-LP4IPfhIO5ZPVOrJo7H8c8Slc0WYTFAUNQX1U0LBPKyXioXhH5H2TawIgxKujIyOhbwoBbpvOsBf6o5+ToJIrQ=="], + + "@types/hapi__mimos": ["@types/hapi__mimos@4.1.4", "", { "dependencies": { "@types/mime-db": "*" } }, "sha512-i9hvJpFYTT/qzB5xKWvDYaSXrIiNqi4ephi+5Lo6+DoQdwqPXQgmVVOZR+s3MBiHoFqsCZCX9TmVWG3HczmTEQ=="], + + "@types/hapi__shot": ["@types/hapi__shot@6.0.0", "", { "dependencies": { "@hapi/shot": "*" } }, "sha512-CUxk0AEQRpIT2cx7EYXkqaXm60LP46ZyEhr/7uov7D/ZLysA5DKkfBtPIYnoWvn8qfcFV/7YHLd3U9gThuhb0A=="], + + "@types/http-assert": ["@types/http-assert@1.5.6", "", {}, "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + + "@types/ioredis4": ["@types/ioredis@4.28.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ=="], + + "@types/keygrip": ["@types/keygrip@1.0.6", "", {}, "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ=="], + + "@types/koa": ["@types/koa@2.13.9", "", { "dependencies": { "@types/accepts": "*", "@types/content-disposition": "*", "@types/cookies": "*", "@types/http-assert": "*", "@types/http-errors": "*", "@types/keygrip": "*", "@types/koa-compose": "*", "@types/node": "*" } }, "sha512-tPX3cN1dGrMn+sjCDEiQqXH2AqlPoPd594S/8zxwUm/ZbPsQXKqHPUypr2gjCPhHUc+nDJLduhh5lXI/1olnGQ=="], + + "@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="], + + "@types/koa__router": ["@types/koa__router@12.0.3", "", { "dependencies": { "@types/koa": "*" } }, "sha512-5YUJVv6NwM1z7m6FuYpKfNLTZ932Z6EF6xy2BbtpJSyn13DKNQEkXVffFVSnJHxvwwWh2SAeumpjAYUELqgjyw=="], + + "@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="], + + "@types/mime-db": ["@types/mime-db@1.43.6", "", {}, "sha512-r2cqxAt/Eo5yWBOQie1lyM1JZFCiORa5xtLlhSZI0w8RJggBPKw8c4g/fgQCzWydaDR5bL4imnmix2d1n52iBw=="], + + "@types/mysql": ["@types/mysql@2.15.22", "", { "dependencies": { "@types/node": "*" } }, "sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ=="], + + "@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="], + + "@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="], + + "@types/pg-pool": ["@types/pg-pool@2.0.4", "", { "dependencies": { "@types/pg": "*" } }, "sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ=="], + + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], + + "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], + + "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], + + "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], + + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-import-assertions": ["acorn-import-assertions@1.9.0", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], + + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + + "body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], + + "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "import-in-the-middle": ["import-in-the-middle@1.7.1", "", { "dependencies": { "acorn": "^8.8.2", "acorn-import-assertions": "^1.9.0", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="], + + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "protobufjs": ["protobufjs@7.5.4", "", { "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" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], + + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + + "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], + + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "@hapi/catbox/@hapi/boom": ["@hapi/boom@10.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA=="], + + "@hapi/catbox/@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="], + + "@hapi/catbox/@hapi/podium": ["@hapi/podium@5.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2", "@hapi/teamwork": "^6.0.0", "@hapi/validate": "^2.0.1" } }, "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA=="], + + "@hapi/catbox/@hapi/validate": ["@hapi/validate@2.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2", "@hapi/topo": "^6.0.1" } }, "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA=="], + + "@hapi/shot/@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="], + + "@hapi/shot/@hapi/validate": ["@hapi/validate@2.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2", "@hapi/topo": "^6.0.1" } }, "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA=="], + + "@opentelemetry/instrumentation-aws-lambda/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-aws-sdk/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-cassandra-driver/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-connect/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-cucumber/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-dns/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-express/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-fastify/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-generic-pool/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-hapi/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-ioredis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-knex/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-koa/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-memcached/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-mongodb/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-mongoose/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-mysql/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-nestjs-core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-net/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-redis/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-redis-4/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-restify/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-socket.io/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/instrumentation-tedious/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/resource-detector-alibaba-cloud/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/resource-detector-aws/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/resource-detector-container/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "@opentelemetry/resource-detector-gcp/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], + + "https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "@hapi/catbox/@hapi/podium/@hapi/teamwork": ["@hapi/teamwork@6.0.1", "", {}, "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ=="], + + "@hapi/catbox/@hapi/validate/@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="], + + "@hapi/shot/@hapi/validate/@hapi/topo": ["@hapi/topo@6.0.2", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg=="], + + "https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + } +} diff --git a/examples/node-express/package.json b/examples/node-express/package.json new file mode 100644 index 0000000..be6f2e4 --- /dev/null +++ b/examples/node-express/package.json @@ -0,0 +1,19 @@ +{ + "name": "hydra-node-example", + "version": "1.0.0", + "description": "Express app with OpenTelemetry sending to Hydra", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "express": "^4.18.2", + "@opentelemetry/api": "^1.7.0", + "@opentelemetry/sdk-node": "^0.46.0", + "@opentelemetry/auto-instrumentations-node": "^0.40.0", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.46.0", + "@opentelemetry/sdk-logs": "^0.46.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.46.0", + "@grpc/grpc-js": "^1.9.0" + } +} diff --git a/examples/node-express/tracing.js b/examples/node-express/tracing.js new file mode 100644 index 0000000..8d23657 --- /dev/null +++ b/examples/node-express/tracing.js @@ -0,0 +1,43 @@ +const { NodeSDK } = require('@opentelemetry/sdk-node'); +const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); +const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); +const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-grpc'); +const { Resource } = require('@opentelemetry/resources'); +const { LoggerProvider, SimpleLogRecordProcessor } = require('@opentelemetry/sdk-logs'); +const { logs } = require('@opentelemetry/api-logs'); + +const resource = new Resource({ + 'service.name': 'demo-node-api', +}); + +// Set up logging +const loggerProvider = new LoggerProvider({ resource }); +loggerProvider.addLogRecordProcessor( + new SimpleLogRecordProcessor( + new OTLPLogExporter({ + url: 'http://localhost:4317', + }) + ) +); +logs.setGlobalLoggerProvider(loggerProvider); + +// Set up tracing +const sdk = new NodeSDK({ + resource, + traceExporter: new OTLPTraceExporter({ + url: 'http://localhost:4317', + }), + instrumentations: [ + getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-fs': { enabled: false }, + }), + ], +}); + +sdk.start(); + +process.on('SIGTERM', () => { + sdk.shutdown() + .then(() => loggerProvider.shutdown()) + .then(() => process.exit(0)); +}); diff --git a/examples/python-fastapi/README.md b/examples/python-fastapi/README.md new file mode 100644 index 0000000..3223d89 --- /dev/null +++ b/examples/python-fastapi/README.md @@ -0,0 +1,42 @@ +# Python FastAPI Example + +A simple FastAPI app instrumented with OpenTelemetry, sending traces and logs to Hydra. + +## Setup + +```bash +# Install dependencies +pip install -r requirements.txt + +# Start Hydra (from project root) +cargo run --release + +# Run the example app +python app.py +``` + +## Test Endpoints + +```bash +# Basic request +curl http://localhost:8000/ + +# Get user (generates DB span) +curl http://localhost:8000/users/123 + +# Slow endpoint +curl http://localhost:8000/slow + +# Error endpoint +curl http://localhost:8000/error + +# Chain of operations +curl http://localhost:8000/chain + +# 404 error +curl http://localhost:8000/users/0 +``` + +## View in Hydra + +Open http://localhost:8080 to see traces and logs. diff --git a/examples/python-fastapi/app.py b/examples/python-fastapi/app.py new file mode 100644 index 0000000..9259031 --- /dev/null +++ b/examples/python-fastapi/app.py @@ -0,0 +1,111 @@ +""" +Example FastAPI app instrumented with OpenTelemetry. +Sends traces and logs to Hydra. +""" +import logging +import random +import time + +from fastapi import FastAPI, HTTPException +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from opentelemetry.instrumentation.logging import LoggingInstrumentor + +# Configure resource +resource = Resource.create({"service.name": "demo-api"}) + +# Configure tracing +trace_provider = TracerProvider(resource=resource) +trace_provider.add_span_processor( + BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)) +) +trace.set_tracer_provider(trace_provider) + +# Configure logging +logger_provider = LoggerProvider(resource=resource) +logger_provider.add_log_record_processor( + BatchLogRecordProcessor(OTLPLogExporter(endpoint="http://localhost:4317", insecure=True)) +) +handler = LoggingHandler(logger_provider=logger_provider) +logging.getLogger().addHandler(handler) +logging.getLogger().setLevel(logging.INFO) + +LoggingInstrumentor().instrument() + +# Create app +app = FastAPI(title="Demo API") +FastAPIInstrumentor.instrument_app(app) + +tracer = trace.get_tracer(__name__) +logger = logging.getLogger(__name__) + + +@app.get("/") +async def root(): + logger.info("Root endpoint called") + return {"message": "Hello from demo-api"} + + +@app.get("/users/{user_id}") +async def get_user(user_id: int): + logger.info(f"Fetching user {user_id}") + + with tracer.start_as_current_span("fetch_from_database") as span: + # Simulate DB call + time.sleep(random.uniform(0.01, 0.05)) + span.set_attribute("db.system", "postgresql") + span.set_attribute("db.operation", "SELECT") + + if user_id == 0: + logger.error("User not found") + raise HTTPException(status_code=404, detail="User not found") + + return {"id": user_id, "name": f"User {user_id}"} + + +@app.get("/slow") +async def slow_endpoint(): + logger.warning("Slow endpoint called - this will take a while") + + with tracer.start_as_current_span("slow_operation"): + time.sleep(random.uniform(0.5, 1.5)) + + return {"status": "done"} + + +@app.get("/error") +async def error_endpoint(): + logger.error("Intentional error triggered") + raise HTTPException(status_code=500, detail="Something went wrong") + + +@app.get("/chain") +async def chain_endpoint(): + """Simulates a chain of operations""" + logger.info("Starting chain operation") + + with tracer.start_as_current_span("step_1_validate"): + time.sleep(random.uniform(0.01, 0.02)) + logger.info("Validation complete") + + with tracer.start_as_current_span("step_2_process"): + time.sleep(random.uniform(0.02, 0.05)) + logger.info("Processing complete") + + with tracer.start_as_current_span("step_3_save"): + time.sleep(random.uniform(0.01, 0.03)) + logger.info("Save complete") + + return {"status": "chain complete"} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/examples/python-fastapi/requirements.txt b/examples/python-fastapi/requirements.txt new file mode 100644 index 0000000..6bd77dd --- /dev/null +++ b/examples/python-fastapi/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.109.0 +uvicorn==0.27.0 +opentelemetry-api==1.22.0 +opentelemetry-sdk==1.22.0 +opentelemetry-exporter-otlp==1.22.0 +opentelemetry-instrumentation-fastapi==0.43b0 +opentelemetry-instrumentation-logging==0.43b0 diff --git a/idea.md b/idea.md new file mode 100644 index 0000000..f293fcc --- /dev/null +++ b/idea.md @@ -0,0 +1,274 @@ +# Hydra: Local-First Dev Observability + +## Problem Statement + +Developers are blind when running services locally. + +Production has Datadog, Grafana, fancy dashboards. Local dev has `println` and `docker logs -f`. + +The observability gap between prod and local is massive: + +- No traces across local microservices +- No correlated logs between containers +- No metrics unless you set up Prometheus yourself +- No way to answer "why is this slow?" without adding debug statements + +Setting up local observability today means: +- Installing Jaeger, Prometheus, Grafana, Loki +- Writing config files for each +- Configuring exporters in every service +- Managing 5+ containers just to see what's happening + +**Most developers give up and just use print statements.** + +--- + +## Solution + +A single binary that gives you full observability for local development. + +```bash +# That's it. You're done. +docker run -p 8080:8080 -p 4317:4317 hydra +``` + +Open `localhost:8080`. See logs, traces, and metrics from all your services. Zero config. + +--- + +## Core Principles + +1. **Zero config by default** + Works out of the box. No YAML. No setup wizards. Just run it. + +2. **Single container** + One image. One port for UI. One port for OTLP. Nothing else. + +3. **Ephemeral by design** + Data lives in memory. Restart clears everything. This is a feature, not a bug. + +4. **Auto-correlation** + Logs, traces, and metrics are linked automatically. Click a trace, see the logs. Click an error, see the span. + +5. **Framework auto-detection** + Detects common frameworks and shows relevant views. Spring Boot? Show HTTP endpoints. Express? Show routes. + +6. **Fast startup** + Under 2 seconds to ready. Developers restart constantly. Every second matters. + +--- + +## Non-Goals + +- Production deployment +- Long-term storage +- Horizontal scaling +- Multi-tenant support +- Alerting +- Dashboards you configure + +This is a dev tool. It dies when you close your laptop. That's fine. + +--- + +## User Experience + +### Starting + +```bash +# Option 1: Docker +docker run -p 8080:8080 -p 4317:4317 ghcr.io/hydra/hydra + +# Option 2: Binary +hydra + +# Option 3: In your docker-compose +services: + hydra: + image: ghcr.io/hydra/hydra + ports: + - "8080:8080" + - "4317:4317" +``` + +### Instrumenting Your App + +Point your OTEL SDK at `localhost:4317`. Done. + +```python +# Python +os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4317" +``` + +```javascript +// Node +process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4317" +``` + +```java +// Java - system property +-Dotel.exporter.otlp.endpoint=http://localhost:4317 +``` + +Or use auto-instrumentation agents that already exist. + +### The UI + +Open `localhost:8080`: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ HYDRA [Services ▼] [⚙] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ LIVE FEED │ +│ ───────────────────────────────────────────────────────── │ +│ 14:32:01 [api-gateway] POST /checkout 200 142ms [→] │ +│ 14:32:01 [payment-svc] Processing payment... [→] │ +│ 14:32:02 [payment-svc] ERROR: Connection refused [→] │ +│ 14:32:02 [api-gateway] POST /checkout 500 1.2s [→] │ +│ │ +│ ───────────────────────────────────────────────────────── │ +│ [→] = Click to see full trace + related logs │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ SERVICES (auto-discovered) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ api-gateway │ │ payment-svc │ │ inventory │ │ +│ │ 142 req/min │ │ 89 req/min │ │ 34 req/min │ │ +│ │ 2 errors │ │ 5 errors │ │ 0 errors │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +Click any request → see the full trace with logs inline: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TRACE: POST /checkout │ +│ ───────────────────────────────────────────────────────── │ +│ │ +│ api-gateway ████████████████████████████████████░░ 1.2s │ +│ ├─ LOG: Received checkout request user_id=123 │ +│ │ │ +│ └─ payment-svc █████████████████████████████░░░ 1.1s │ +│ ├─ LOG: Processing payment amount=99.00 │ +│ ├─ LOG: Connecting to payment gateway... │ +│ ├─ LOG: ERROR: Connection refused │ +│ └─ LOG: Retrying... (attempt 2/3) │ +│ │ +│ [Copy trace ID] [Export as JSON] [Show raw spans] │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Architecture + +``` +┌─────────────────────────────────────────┐ +│ Your Services │ +│ (with OTEL SDK pointing to :4317) │ +└──────────────────┬──────────────────────┘ + │ OTLP (gRPC/HTTP) + ▼ +┌─────────────────────────────────────────┐ +│ HYDRA │ +│ ┌───────────┐ ┌───────────────────┐ │ +│ │ OTLP │ │ In-Memory Store │ │ +│ │ Receiver │─▶│ - Traces │ │ +│ │ :4317 │ │ - Logs │ │ +│ └───────────┘ │ - Metrics │ │ +│ │ - Correlations │ │ +│ ┌───────────┐ └─────────┬─────────┘ │ +│ │ Web UI │◀───────────┘ │ +│ │ :8080 │ (WebSocket for live) │ +│ └───────────┘ │ +└─────────────────────────────────────────┘ +``` + +All in one process. All in memory. Simple. + +--- + +## Technology Choices + +- Language: Rust +- OTLP: `opentelemetry-rust` + `tonic` +- Web server: `axum` +- UI: Embedded SPA (Svelte or Solid, compiled into binary) +- Storage: In-memory (bounded ring buffers) +- WebSocket: Live streaming updates to UI + +--- + +## Data Model + +### Retention + +- Keep last N traces (default: 1000) +- Keep last N logs per service (default: 10000) +- Keep last 1 hour of metrics +- Ring buffer eviction (oldest out) + +### Correlation + +Every piece of data is linked: + +``` +Trace + └─ Spans[] + └─ Logs[] (by trace_id + span_id + time window) + └─ Metrics[] (by service + time window) +``` + +Correlation happens at ingest, not query time. + +--- + +## MVP Scope + +### Phase 1: Core + +- [ ] OTLP gRPC receiver (traces + logs) +- [ ] In-memory storage with ring buffers +- [ ] Basic web UI (live feed, trace view) +- [ ] Auto-correlation of logs to traces +- [ ] Single binary, single container + +### Phase 2: Polish + +- [ ] OTLP HTTP receiver +- [ ] Metrics support +- [ ] Service dependency graph +- [ ] Search/filter in UI +- [ ] Dark mode (obviously) + +### Phase 3: DX + +- [ ] `hydra init` - generates docker-compose snippet +- [ ] Framework detection (show relevant info per framework) +- [ ] "Why is this slow?" button (highlights slow spans) +- [ ] Export trace to share with teammate + +--- + +## Success Criteria + +The project succeeds if: + +- A developer can go from zero to seeing traces in under 60 seconds +- No documentation is needed for basic usage +- It uses less than 200MB of memory for typical local dev +- Developers actually prefer it over print statements + +--- + +## What This Is Not + +- Not a Jaeger replacement (Jaeger is for prod) +- Not a Grafana replacement (Grafana is for dashboards) +- Not a log aggregator (use Loki for that) +- Not trying to scale (scaling is a non-goal) + +This is **observability for `localhost`**. Nothing more. Nothing less. diff --git a/proto/opentelemetry/proto/collector/logs/v1/logs_service.proto b/proto/opentelemetry/proto/collector/logs/v1/logs_service.proto new file mode 100644 index 0000000..af982d0 --- /dev/null +++ b/proto/opentelemetry/proto/collector/logs/v1/logs_service.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package opentelemetry.proto.collector.logs.v1; + +import "opentelemetry/proto/logs/v1/logs.proto"; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.collector.logs.v1"; +option java_outer_classname = "LogsServiceProto"; + +service LogsService { + rpc Export(ExportLogsServiceRequest) returns (ExportLogsServiceResponse) {} +} + +message ExportLogsServiceRequest { + repeated opentelemetry.proto.logs.v1.ResourceLogs resource_logs = 1; +} + +message ExportLogsServiceResponse { + ExportLogsPartialSuccess partial_success = 1; +} + +message ExportLogsPartialSuccess { + int64 rejected_log_records = 1; + string error_message = 2; +} diff --git a/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto b/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto new file mode 100644 index 0000000..e55cfff --- /dev/null +++ b/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package opentelemetry.proto.collector.trace.v1; + +import "opentelemetry/proto/trace/v1/trace.proto"; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.collector.trace.v1"; +option java_outer_classname = "TraceServiceProto"; + +service TraceService { + rpc Export(ExportTraceServiceRequest) returns (ExportTraceServiceResponse) {} +} + +message ExportTraceServiceRequest { + repeated opentelemetry.proto.trace.v1.ResourceSpans resource_spans = 1; +} + +message ExportTraceServiceResponse { + ExportTracePartialSuccess partial_success = 1; +} + +message ExportTracePartialSuccess { + int64 rejected_spans = 1; + string error_message = 2; +} diff --git a/proto/opentelemetry/proto/common/v1/common.proto b/proto/opentelemetry/proto/common/v1/common.proto new file mode 100644 index 0000000..be8f777 --- /dev/null +++ b/proto/opentelemetry/proto/common/v1/common.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package opentelemetry.proto.common.v1; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.common.v1"; +option java_outer_classname = "CommonProto"; + +message AnyValue { + oneof value { + string string_value = 1; + bool bool_value = 2; + int64 int_value = 3; + double double_value = 4; + ArrayValue array_value = 5; + KeyValueList kvlist_value = 6; + bytes bytes_value = 7; + } +} + +message ArrayValue { + repeated AnyValue values = 1; +} + +message KeyValueList { + repeated KeyValue values = 1; +} + +message KeyValue { + string key = 1; + AnyValue value = 2; +} + +message InstrumentationScope { + string name = 1; + string version = 2; + repeated KeyValue attributes = 3; + uint32 dropped_attributes_count = 4; +} diff --git a/proto/opentelemetry/proto/logs/v1/logs.proto b/proto/opentelemetry/proto/logs/v1/logs.proto new file mode 100644 index 0000000..60f9426 --- /dev/null +++ b/proto/opentelemetry/proto/logs/v1/logs.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package opentelemetry.proto.logs.v1; + +import "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.logs.v1"; +option java_outer_classname = "LogsProto"; + +message LogsData { + repeated ResourceLogs resource_logs = 1; +} + +message ResourceLogs { + opentelemetry.proto.resource.v1.Resource resource = 1; + repeated ScopeLogs scope_logs = 2; + string schema_url = 3; +} + +message ScopeLogs { + opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + repeated LogRecord log_records = 2; + string schema_url = 3; +} + +message LogRecord { + fixed64 time_unix_nano = 1; + fixed64 observed_time_unix_nano = 11; + SeverityNumber severity_number = 2; + string severity_text = 3; + opentelemetry.proto.common.v1.AnyValue body = 5; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 6; + uint32 dropped_attributes_count = 7; + fixed32 flags = 8; + bytes trace_id = 9; + bytes span_id = 10; +} + +enum SeverityNumber { + SEVERITY_NUMBER_UNSPECIFIED = 0; + SEVERITY_NUMBER_TRACE = 1; + SEVERITY_NUMBER_TRACE2 = 2; + SEVERITY_NUMBER_TRACE3 = 3; + SEVERITY_NUMBER_TRACE4 = 4; + SEVERITY_NUMBER_DEBUG = 5; + SEVERITY_NUMBER_DEBUG2 = 6; + SEVERITY_NUMBER_DEBUG3 = 7; + SEVERITY_NUMBER_DEBUG4 = 8; + SEVERITY_NUMBER_INFO = 9; + SEVERITY_NUMBER_INFO2 = 10; + SEVERITY_NUMBER_INFO3 = 11; + SEVERITY_NUMBER_INFO4 = 12; + SEVERITY_NUMBER_WARN = 13; + SEVERITY_NUMBER_WARN2 = 14; + SEVERITY_NUMBER_WARN3 = 15; + SEVERITY_NUMBER_WARN4 = 16; + SEVERITY_NUMBER_ERROR = 17; + SEVERITY_NUMBER_ERROR2 = 18; + SEVERITY_NUMBER_ERROR3 = 19; + SEVERITY_NUMBER_ERROR4 = 20; + SEVERITY_NUMBER_FATAL = 21; + SEVERITY_NUMBER_FATAL2 = 22; + SEVERITY_NUMBER_FATAL3 = 23; + SEVERITY_NUMBER_FATAL4 = 24; +} diff --git a/proto/opentelemetry/proto/resource/v1/resource.proto b/proto/opentelemetry/proto/resource/v1/resource.proto new file mode 100644 index 0000000..6f9700d --- /dev/null +++ b/proto/opentelemetry/proto/resource/v1/resource.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package opentelemetry.proto.resource.v1; + +import "opentelemetry/proto/common/v1/common.proto"; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.resource.v1"; +option java_outer_classname = "ResourceProto"; + +message Resource { + repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; + uint32 dropped_attributes_count = 2; +} diff --git a/proto/opentelemetry/proto/trace/v1/trace.proto b/proto/opentelemetry/proto/trace/v1/trace.proto new file mode 100644 index 0000000..2bf7f15 --- /dev/null +++ b/proto/opentelemetry/proto/trace/v1/trace.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +package opentelemetry.proto.trace.v1; + +import "opentelemetry/proto/common/v1/common.proto"; +import "opentelemetry/proto/resource/v1/resource.proto"; + +option java_multiple_files = true; +option java_package = "io.opentelemetry.proto.trace.v1"; +option java_outer_classname = "TraceProto"; + +message TracesData { + repeated ResourceSpans resource_spans = 1; +} + +message ResourceSpans { + opentelemetry.proto.resource.v1.Resource resource = 1; + repeated ScopeSpans scope_spans = 2; + string schema_url = 3; +} + +message ScopeSpans { + opentelemetry.proto.common.v1.InstrumentationScope scope = 1; + repeated Span spans = 2; + string schema_url = 3; +} + +message Span { + bytes trace_id = 1; + bytes span_id = 2; + string trace_state = 3; + bytes parent_span_id = 4; + string name = 5; + SpanKind kind = 6; + fixed64 start_time_unix_nano = 7; + fixed64 end_time_unix_nano = 8; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; + uint32 dropped_attributes_count = 10; + repeated Event events = 11; + uint32 dropped_events_count = 12; + repeated Link links = 13; + uint32 dropped_links_count = 14; + Status status = 15; +} + +enum SpanKind { + SPAN_KIND_UNSPECIFIED = 0; + SPAN_KIND_INTERNAL = 1; + SPAN_KIND_SERVER = 2; + SPAN_KIND_CLIENT = 3; + SPAN_KIND_PRODUCER = 4; + SPAN_KIND_CONSUMER = 5; +} + +message Status { + string message = 2; + StatusCode code = 3; + + enum StatusCode { + STATUS_CODE_UNSET = 0; + STATUS_CODE_OK = 1; + STATUS_CODE_ERROR = 2; + } +} + +message Event { + fixed64 time_unix_nano = 1; + string name = 2; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; + uint32 dropped_attributes_count = 4; +} + +message Link { + bytes trace_id = 1; + bytes span_id = 2; + string trace_state = 3; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 4; + uint32 dropped_attributes_count = 5; +} diff --git a/src/api/handlers.rs b/src/api/handlers.rs new file mode 100644 index 0000000..d7c0019 --- /dev/null +++ b/src/api/handlers.rs @@ -0,0 +1,94 @@ +//! HTTP request handlers + +use crate::model::{LogRecord, Service, Span}; +use crate::storage::Store; +use axum::{ + extract::{Path, Query, State}, + http::{header, StatusCode, Uri}, + response::{Html, IntoResponse, Response}, + Json, +}; +use rust_embed::Embed; +use serde::{Deserialize, Serialize}; + +#[derive(Embed)] +#[folder = "ui/dist"] +struct UiAssets; + +#[derive(Debug, Deserialize)] +pub struct ListParams { + #[serde(default = "default_limit")] + pub limit: usize, +} + +fn default_limit() -> usize { + 100 +} + +pub async fn health() -> &'static str { + "ok" +} + +pub async fn list_services(State(store): State) -> Json> { + Json(store.get_services()) +} + +pub async fn list_spans( + State(store): State, + Query(params): Query, +) -> Json> { + Json(store.get_recent_spans(params.limit)) +} + +pub async fn list_logs( + State(store): State, + Query(params): Query, +) -> Json> { + Json(store.get_recent_logs(params.limit)) +} + +#[derive(Serialize)] +pub struct TraceResponse { + pub trace_id: String, + pub spans: Vec, + pub logs: Vec, +} + +pub async fn get_trace( + State(store): State, + Path(trace_id): Path, +) -> Result, StatusCode> { + let spans = store.get_trace(&trace_id).unwrap_or_default(); + let logs = store.get_logs_for_trace(&trace_id); + + // Return 404 only if we have neither spans nor logs + if spans.is_empty() && logs.is_empty() { + return Err(StatusCode::NOT_FOUND); + } + + Ok(Json(TraceResponse { + trace_id, + spans, + logs, + })) +} + +pub async fn serve_ui(uri: Uri) -> Response { + let path = uri.path().trim_start_matches('/'); + + // Try to serve the exact file + if let Some(content) = UiAssets::get(path) { + let mime = mime_guess::from_path(path).first_or_octet_stream(); + return ( + [(header::CONTENT_TYPE, mime.as_ref())], + content.data.into_owned(), + ) + .into_response(); + } + + // Fallback to index.html for SPA routing + match UiAssets::get("index.html") { + Some(content) => Html(String::from_utf8_lossy(&content.data).to_string()).into_response(), + None => (StatusCode::OK, Html(include_str!("../../ui/fallback.html"))).into_response(), + } +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..8d47def --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,14 @@ +//! HTTP API and Web UI + +mod handlers; +mod routes; + +use crate::storage::Store; +use std::net::SocketAddr; + +pub async fn serve(addr: SocketAddr, store: Store) -> Result<(), std::io::Error> { + let app = routes::create_router(store); + + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app).await +} diff --git a/src/api/routes.rs b/src/api/routes.rs new file mode 100644 index 0000000..60bc85d --- /dev/null +++ b/src/api/routes.rs @@ -0,0 +1,29 @@ +//! API route definitions + +use crate::api::handlers; +use crate::storage::Store; +use axum::{ + routing::get, + Router, +}; +use tower_http::cors::{Any, CorsLayer}; + +pub fn create_router(store: Store) -> Router { + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + + Router::new() + // API routes + .route("/api/services", get(handlers::list_services)) + .route("/api/spans", get(handlers::list_spans)) + .route("/api/logs", get(handlers::list_logs)) + .route("/api/traces/:trace_id", get(handlers::get_trace)) + // Health check + .route("/health", get(handlers::health)) + // UI (serve index.html for all other routes) + .fallback(handlers::serve_ui) + .layer(cors) + .with_state(store) +} diff --git a/src/ingest/convert.rs b/src/ingest/convert.rs new file mode 100644 index 0000000..bb1ecd5 --- /dev/null +++ b/src/ingest/convert.rs @@ -0,0 +1,214 @@ +//! Convert OTLP proto types to internal model + +use crate::model::{LogRecord, Severity, Span, SpanKind, SpanStatus, StatusCode}; +use crate::proto::{ + AnyValue, KeyValue, ProtoLogRecord, ProtoSpan, ProtoSpanKind, ProtoStatus, + ProtoStatusCode, Resource, Value, +}; +use chrono::{DateTime, Utc}; +use std::collections::HashMap; + +pub fn convert_span(proto: &ProtoSpan, resource: Option<&Resource>) -> Span { + let service_name = resource + .and_then(|r| extract_service_name(&r.attributes)) + .unwrap_or_else(|| "unknown".to_string()); + + let start_time = nanos_to_datetime(proto.start_time_unix_nano); + let end_time = nanos_to_datetime(proto.end_time_unix_nano); + let duration_ms = + (proto.end_time_unix_nano - proto.start_time_unix_nano) as f64 / 1_000_000.0; + + Span { + trace_id: hex::encode(&proto.trace_id), + span_id: hex::encode(&proto.span_id), + parent_span_id: if proto.parent_span_id.is_empty() { + None + } else { + Some(hex::encode(&proto.parent_span_id)) + }, + name: proto.name.clone(), + service_name, + kind: convert_span_kind(proto.kind()), + start_time, + end_time, + duration_ms, + status: proto + .status + .as_ref() + .map(convert_status) + .unwrap_or_default(), + attributes: convert_attributes(&proto.attributes), + } +} + +pub fn convert_log(proto: &ProtoLogRecord, resource: Option<&Resource>) -> LogRecord { + let service_name = resource + .and_then(|r| extract_service_name(&r.attributes)) + .unwrap_or_else(|| "unknown".to_string()); + + let timestamp = if proto.time_unix_nano > 0 { + nanos_to_datetime(proto.time_unix_nano) + } else { + nanos_to_datetime(proto.observed_time_unix_nano) + }; + + LogRecord { + timestamp, + service_name, + severity: Severity::from_number(proto.severity_number), + body: extract_body(&proto.body), + trace_id: if proto.trace_id.is_empty() { + None + } else { + Some(hex::encode(&proto.trace_id)) + }, + span_id: if proto.span_id.is_empty() { + None + } else { + Some(hex::encode(&proto.span_id)) + }, + attributes: convert_attributes(&proto.attributes), + } +} + +fn convert_span_kind(kind: ProtoSpanKind) -> SpanKind { + match kind { + ProtoSpanKind::Server => SpanKind::Server, + ProtoSpanKind::Client => SpanKind::Client, + ProtoSpanKind::Producer => SpanKind::Producer, + ProtoSpanKind::Consumer => SpanKind::Consumer, + _ => SpanKind::Internal, + } +} + +fn convert_status(status: &ProtoStatus) -> SpanStatus { + SpanStatus { + code: match status.code() { + ProtoStatusCode::Ok => StatusCode::Ok, + ProtoStatusCode::Error => StatusCode::Error, + _ => StatusCode::Unset, + }, + message: if status.message.is_empty() { + None + } else { + Some(status.message.clone()) + }, + } +} + +fn convert_attributes(attrs: &[KeyValue]) -> HashMap { + attrs + .iter() + .filter_map(|kv| { + kv.value + .as_ref() + .and_then(|v| extract_string_value(v)) + .map(|val| (kv.key.clone(), val)) + }) + .collect() +} + +fn extract_service_name(attrs: &[KeyValue]) -> Option { + attrs + .iter() + .find(|kv| kv.key == "service.name") + .and_then(|kv| kv.value.as_ref()) + .and_then(extract_string_value) +} + +fn extract_string_value(value: &AnyValue) -> Option { + match &value.value { + Some(Value::StringValue(s)) => Some(s.clone()), + Some(Value::IntValue(i)) => Some(i.to_string()), + Some(Value::DoubleValue(d)) => Some(d.to_string()), + Some(Value::BoolValue(b)) => Some(b.to_string()), + _ => None, + } +} + +fn extract_body(body: &Option) -> String { + body.as_ref() + .and_then(extract_string_value) + .unwrap_or_default() +} + +fn nanos_to_datetime(nanos: u64) -> DateTime { + DateTime::from_timestamp_nanos(nanos as i64) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_nanos_to_datetime() { + let nanos: u64 = 1704067200_000_000_000; // 2024-01-01 00:00:00 UTC + let dt = nanos_to_datetime(nanos); + assert_eq!(dt.format("%Y-%m-%d").to_string(), "2024-01-01"); + } + + #[test] + fn test_convert_attributes() { + let attrs = vec![ + KeyValue { + key: "http.method".to_string(), + value: Some(AnyValue { + value: Some(Value::StringValue("GET".to_string())), + }), + }, + KeyValue { + key: "http.status_code".to_string(), + value: Some(AnyValue { + value: Some(Value::IntValue(200)), + }), + }, + ]; + + let result = convert_attributes(&attrs); + assert_eq!(result.get("http.method"), Some(&"GET".to_string())); + assert_eq!(result.get("http.status_code"), Some(&"200".to_string())); + } + + #[test] + fn test_extract_service_name() { + let attrs = vec![KeyValue { + key: "service.name".to_string(), + value: Some(AnyValue { + value: Some(Value::StringValue("api-gateway".to_string())), + }), + }]; + + assert_eq!( + extract_service_name(&attrs), + Some("api-gateway".to_string()) + ); + } + + #[test] + fn test_extract_service_name_missing() { + let attrs = vec![KeyValue { + key: "other.attr".to_string(), + value: Some(AnyValue { + value: Some(Value::StringValue("value".to_string())), + }), + }]; + + assert_eq!(extract_service_name(&attrs), None); + } + + #[test] + fn test_convert_span_kind() { + assert_eq!(convert_span_kind(ProtoSpanKind::Server), SpanKind::Server); + assert_eq!(convert_span_kind(ProtoSpanKind::Client), SpanKind::Client); + assert_eq!( + convert_span_kind(ProtoSpanKind::Unspecified), + SpanKind::Internal + ); + } + + #[test] + fn test_severity_from_number() { + assert_eq!(Severity::from_number(9), Severity::Info); + assert_eq!(Severity::from_number(17), Severity::Error); + } +} diff --git a/src/ingest/grpc.rs b/src/ingest/grpc.rs new file mode 100644 index 0000000..2b53bc3 --- /dev/null +++ b/src/ingest/grpc.rs @@ -0,0 +1,93 @@ +//! OTLP gRPC server implementation + +use crate::ingest::convert::{convert_log, convert_span}; +use crate::proto::{ + ExportLogsServiceRequest, ExportLogsServiceResponse, ExportTraceServiceRequest, + ExportTraceServiceResponse, LogsService, LogsServiceServer, TraceService, + TraceServiceServer, +}; +use crate::storage::Store; +use std::net::SocketAddr; +use tonic::{transport::Server, Request, Response, Status}; +use tracing::debug; + +pub async fn serve(addr: SocketAddr, store: Store) -> Result<(), tonic::transport::Error> { + let trace_service = TraceServiceImpl { + store: store.clone(), + }; + let logs_service = LogsServiceImpl { store }; + + Server::builder() + .add_service(TraceServiceServer::new(trace_service)) + .add_service(LogsServiceServer::new(logs_service)) + .serve(addr) + .await +} + +struct TraceServiceImpl { + store: Store, +} + +#[tonic::async_trait] +impl TraceService for TraceServiceImpl { + async fn export( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + + for resource_spans in &req.resource_spans { + let resource = resource_spans.resource.as_ref(); + + for scope_spans in &resource_spans.scope_spans { + for span in &scope_spans.spans { + let converted = convert_span(span, resource); + debug!( + trace_id = %converted.trace_id, + name = %converted.name, + "Received span" + ); + self.store.insert_span(converted); + } + } + } + + Ok(Response::new(ExportTraceServiceResponse { + partial_success: None, + })) + } +} + +struct LogsServiceImpl { + store: Store, +} + +#[tonic::async_trait] +impl LogsService for LogsServiceImpl { + async fn export( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + + for resource_logs in &req.resource_logs { + let resource = resource_logs.resource.as_ref(); + + for scope_logs in &resource_logs.scope_logs { + for log in &scope_logs.log_records { + let converted = convert_log(log, resource); + debug!( + service = %converted.service_name, + severity = ?converted.severity, + "Received log" + ); + self.store.insert_log(converted); + } + } + } + + Ok(Response::new(ExportLogsServiceResponse { + partial_success: None, + })) + } +} diff --git a/src/ingest/mod.rs b/src/ingest/mod.rs new file mode 100644 index 0000000..f988212 --- /dev/null +++ b/src/ingest/mod.rs @@ -0,0 +1,4 @@ +//! OTLP ingestion handlers + +pub mod grpc; +mod convert; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3730178 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,43 @@ +mod ingest; +mod storage; +mod api; +mod model; +mod proto; + +use std::net::SocketAddr; +use tokio::signal; +use tracing::{info, Level}; +use tracing_subscriber::FmtSubscriber; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Initialize logging + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::INFO) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + + info!("Starting Hydra - Local Dev Observability"); + + // Initialize shared storage + let store = storage::Store::new(); + + // Start OTLP gRPC server + let grpc_addr: SocketAddr = "0.0.0.0:4317".parse()?; + let grpc_handle = tokio::spawn(ingest::grpc::serve(grpc_addr, store.clone())); + info!("OTLP gRPC listening on {}", grpc_addr); + + // Start HTTP API + UI server + let http_addr: SocketAddr = "0.0.0.0:8080".parse()?; + let http_handle = tokio::spawn(api::serve(http_addr, store.clone())); + info!("Web UI available at http://{}", http_addr); + + // Wait for shutdown signal + signal::ctrl_c().await?; + info!("Shutting down..."); + + grpc_handle.abort(); + http_handle.abort(); + + Ok(()) +} diff --git a/src/model/log.rs b/src/model/log.rs new file mode 100644 index 0000000..3e443f9 --- /dev/null +++ b/src/model/log.rs @@ -0,0 +1,79 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Internal log record representation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LogRecord { + pub timestamp: DateTime, + pub service_name: String, + pub severity: Severity, + pub body: String, + pub trace_id: Option, + pub span_id: Option, + pub attributes: HashMap, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "lowercase")] +pub enum Severity { + Trace, + Debug, + Info, + Warn, + Error, + Fatal, +} + +impl Severity { + pub fn from_number(n: i32) -> Self { + match n { + 1..=4 => Severity::Trace, + 5..=8 => Severity::Debug, + 9..=12 => Severity::Info, + 13..=16 => Severity::Warn, + 17..=20 => Severity::Error, + 21..=24 => Severity::Fatal, + _ => Severity::Info, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_severity_from_number() { + assert_eq!(Severity::from_number(1), Severity::Trace); + assert_eq!(Severity::from_number(4), Severity::Trace); + assert_eq!(Severity::from_number(5), Severity::Debug); + assert_eq!(Severity::from_number(9), Severity::Info); + assert_eq!(Severity::from_number(13), Severity::Warn); + assert_eq!(Severity::from_number(17), Severity::Error); + assert_eq!(Severity::from_number(21), Severity::Fatal); + } + + #[test] + fn test_severity_default_for_unknown() { + assert_eq!(Severity::from_number(0), Severity::Info); + assert_eq!(Severity::from_number(100), Severity::Info); + assert_eq!(Severity::from_number(-1), Severity::Info); + } + + #[test] + fn test_severity_ordering() { + assert!(Severity::Trace < Severity::Debug); + assert!(Severity::Debug < Severity::Info); + assert!(Severity::Info < Severity::Warn); + assert!(Severity::Warn < Severity::Error); + assert!(Severity::Error < Severity::Fatal); + } + + #[test] + fn test_severity_error_threshold() { + assert!(Severity::Error >= Severity::Error); + assert!(Severity::Fatal >= Severity::Error); + assert!(!(Severity::Warn >= Severity::Error)); + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..f1af6df --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,9 @@ +//! Internal data models - simplified from OTLP + +mod span; +mod log; +mod service; + +pub use span::{Span, SpanKind, SpanStatus, StatusCode}; +pub use log::{LogRecord, Severity}; +pub use service::Service; diff --git a/src/model/service.rs b/src/model/service.rs new file mode 100644 index 0000000..6ebd945 --- /dev/null +++ b/src/model/service.rs @@ -0,0 +1,27 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// Auto-discovered service info +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Service { + pub name: String, + pub first_seen: DateTime, + pub last_seen: DateTime, + pub span_count: u64, + pub log_count: u64, + pub error_count: u64, +} + +impl Service { + pub fn new(name: String) -> Self { + let now = Utc::now(); + Self { + name, + first_seen: now, + last_seen: now, + span_count: 0, + log_count: 0, + error_count: 0, + } + } +} diff --git a/src/model/span.rs b/src/model/span.rs new file mode 100644 index 0000000..59e30ce --- /dev/null +++ b/src/model/span.rs @@ -0,0 +1,137 @@ +#![allow(dead_code)] + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Internal span representation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Span { + pub trace_id: String, + pub span_id: String, + pub parent_span_id: Option, + pub name: String, + pub service_name: String, + pub kind: SpanKind, + pub start_time: DateTime, + pub end_time: DateTime, + pub duration_ms: f64, + pub status: SpanStatus, + pub attributes: HashMap, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum SpanKind { + Internal, + Server, + Client, + Producer, + Consumer, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct SpanStatus { + pub code: StatusCode, + pub message: Option, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum StatusCode { + Unset, + Ok, + Error, +} + +impl Default for SpanStatus { + fn default() -> Self { + Self { + code: StatusCode::Unset, + message: None, + } + } +} + +impl Span { + /// Check if this span is a root span (no parent) + pub fn is_root(&self) -> bool { + self.parent_span_id.is_none() + } + + /// Check if this span has an error status + pub fn is_error(&self) -> bool { + self.status.code == StatusCode::Error + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + + fn make_test_span() -> Span { + Span { + trace_id: "abc123".to_string(), + span_id: "span1".to_string(), + parent_span_id: None, + name: "GET /api/users".to_string(), + service_name: "api-gateway".to_string(), + kind: SpanKind::Server, + start_time: Utc::now(), + end_time: Utc::now(), + duration_ms: 42.5, + status: SpanStatus::default(), + attributes: HashMap::new(), + } + } + + #[test] + fn test_is_root_span() { + let span = make_test_span(); + assert!(span.is_root()); + + let child_span = Span { + parent_span_id: Some("parent123".to_string()), + ..make_test_span() + }; + assert!(!child_span.is_root()); + } + + #[test] + fn test_is_error() { + let ok_span = Span { + status: SpanStatus { + code: StatusCode::Ok, + message: None, + }, + ..make_test_span() + }; + assert!(!ok_span.is_error()); + + let error_span = Span { + status: SpanStatus { + code: StatusCode::Error, + message: Some("connection refused".to_string()), + }, + ..make_test_span() + }; + assert!(error_span.is_error()); + } + + #[test] + fn test_default_status() { + let status = SpanStatus::default(); + assert_eq!(status.code, StatusCode::Unset); + assert!(status.message.is_none()); + } + + #[test] + fn test_span_serialization() { + let span = make_test_span(); + let json = serde_json::to_string(&span).unwrap(); + + assert!(json.contains("\"trace_id\":\"abc123\"")); + assert!(json.contains("\"kind\":\"server\"")); + } +} diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..958662e --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,19 @@ +//! Re-export OpenTelemetry proto types + +pub use opentelemetry_proto::tonic::collector::{ + logs::v1::{ + logs_service_server::{LogsService, LogsServiceServer}, + ExportLogsServiceRequest, ExportLogsServiceResponse, + }, + trace::v1::{ + trace_service_server::{TraceService, TraceServiceServer}, + ExportTraceServiceRequest, ExportTraceServiceResponse, + }, +}; + +pub use opentelemetry_proto::tonic::{ + common::v1::{any_value::Value, AnyValue, KeyValue}, + logs::v1::LogRecord as ProtoLogRecord, + resource::v1::Resource, + trace::v1::{span::SpanKind as ProtoSpanKind, Span as ProtoSpan, Status as ProtoStatus, status::StatusCode as ProtoStatusCode}, +}; diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 0000000..5870837 --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,7 @@ +//! In-memory storage with ring buffers + +mod store; +mod ring_buffer; + +pub use store::Store; +pub use ring_buffer::RingBuffer; diff --git a/src/storage/ring_buffer.rs b/src/storage/ring_buffer.rs new file mode 100644 index 0000000..8eb2bb3 --- /dev/null +++ b/src/storage/ring_buffer.rs @@ -0,0 +1,123 @@ +#![allow(dead_code)] + +use std::collections::VecDeque; + +/// Fixed-size ring buffer that evicts oldest items +#[derive(Debug)] +pub struct RingBuffer { + buffer: VecDeque, + capacity: usize, +} + +impl RingBuffer { + pub fn new(capacity: usize) -> Self { + Self { + buffer: VecDeque::with_capacity(capacity), + capacity, + } + } + + pub fn push(&mut self, item: T) { + if self.buffer.len() >= self.capacity { + self.buffer.pop_front(); + } + self.buffer.push_back(item); + } + + pub fn iter(&self) -> impl Iterator { + self.buffer.iter() + } + + pub fn len(&self) -> usize { + self.buffer.len() + } + + pub fn is_empty(&self) -> bool { + self.buffer.is_empty() + } + + /// Get items in reverse order (newest first) + pub fn iter_rev(&self) -> impl Iterator { + self.buffer.iter().rev() + } +} + +impl RingBuffer { + pub fn to_vec(&self) -> Vec { + self.buffer.iter().cloned().collect() + } + + pub fn to_vec_rev(&self) -> Vec { + self.buffer.iter().rev().cloned().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_push_within_capacity() { + let mut buf: RingBuffer = RingBuffer::new(5); + buf.push(1); + buf.push(2); + buf.push(3); + + assert_eq!(buf.len(), 3); + assert_eq!(buf.to_vec(), vec![1, 2, 3]); + } + + #[test] + fn test_eviction_when_full() { + let mut buf: RingBuffer = RingBuffer::new(3); + buf.push(1); + buf.push(2); + buf.push(3); + buf.push(4); // should evict 1 + + assert_eq!(buf.len(), 3); + assert_eq!(buf.to_vec(), vec![2, 3, 4]); + } + + #[test] + fn test_multiple_evictions() { + let mut buf: RingBuffer = RingBuffer::new(2); + buf.push(1); + buf.push(2); + buf.push(3); + buf.push(4); + buf.push(5); + + assert_eq!(buf.len(), 2); + assert_eq!(buf.to_vec(), vec![4, 5]); + } + + #[test] + fn test_to_vec_rev() { + let mut buf: RingBuffer = RingBuffer::new(5); + buf.push(1); + buf.push(2); + buf.push(3); + + assert_eq!(buf.to_vec_rev(), vec![3, 2, 1]); + } + + #[test] + fn test_empty_buffer() { + let buf: RingBuffer = RingBuffer::new(5); + assert!(buf.is_empty()); + assert_eq!(buf.len(), 0); + assert_eq!(buf.to_vec(), Vec::::new()); + } + + #[test] + fn test_capacity_one() { + let mut buf: RingBuffer<&str> = RingBuffer::new(1); + buf.push("a"); + buf.push("b"); + buf.push("c"); + + assert_eq!(buf.len(), 1); + assert_eq!(buf.to_vec(), vec!["c"]); + } +} diff --git a/src/storage/store.rs b/src/storage/store.rs new file mode 100644 index 0000000..3b91080 --- /dev/null +++ b/src/storage/store.rs @@ -0,0 +1,302 @@ +#![allow(dead_code)] + +use crate::model::{LogRecord, Service, Severity, Span, StatusCode}; +use crate::storage::RingBuffer; +use chrono::Utc; +use dashmap::DashMap; +use parking_lot::RwLock; +use std::sync::Arc; + +const MAX_SPANS: usize = 10_000; +const MAX_LOGS: usize = 50_000; + +/// Thread-safe in-memory store +#[derive(Clone)] +pub struct Store { + inner: Arc, +} + +struct StoreInner { + spans: RwLock>, + logs: RwLock>, + services: DashMap, + /// trace_id -> spans for quick lookup + traces: DashMap>, +} + +impl Store { + pub fn new() -> Self { + Self { + inner: Arc::new(StoreInner { + spans: RwLock::new(RingBuffer::new(MAX_SPANS)), + logs: RwLock::new(RingBuffer::new(MAX_LOGS)), + services: DashMap::new(), + traces: DashMap::new(), + }), + } + } + + pub fn insert_span(&self, span: Span) { + let service_name = span.service_name.clone(); + let trace_id = span.trace_id.clone(); + let is_error = span.status.code == StatusCode::Error; + + // Update service stats + self.inner + .services + .entry(service_name.clone()) + .and_modify(|s| { + s.last_seen = Utc::now(); + s.span_count += 1; + if is_error { + s.error_count += 1; + } + }) + .or_insert_with(|| { + let mut svc = Service::new(service_name); + svc.span_count = 1; + if is_error { + svc.error_count = 1; + } + svc + }); + + // Add to trace index + self.inner + .traces + .entry(trace_id) + .or_default() + .push(span.clone()); + + // Add to ring buffer + self.inner.spans.write().push(span); + } + + pub fn insert_log(&self, log: LogRecord) { + let service_name = log.service_name.clone(); + let is_error = log.severity >= Severity::Error; + + // Update service stats + self.inner + .services + .entry(service_name.clone()) + .and_modify(|s| { + s.last_seen = Utc::now(); + s.log_count += 1; + if is_error { + s.error_count += 1; + } + }) + .or_insert_with(|| { + let mut svc = Service::new(service_name); + svc.log_count = 1; + if is_error { + svc.error_count = 1; + } + svc + }); + + self.inner.logs.write().push(log); + } + + pub fn get_recent_spans(&self, limit: usize) -> Vec { + self.inner + .spans + .read() + .to_vec_rev() + .into_iter() + .take(limit) + .collect() + } + + pub fn get_recent_logs(&self, limit: usize) -> Vec { + self.inner + .logs + .read() + .to_vec_rev() + .into_iter() + .take(limit) + .collect() + } + + pub fn get_trace(&self, trace_id: &str) -> Option> { + self.inner.traces.get(trace_id).map(|v| v.clone()) + } + + pub fn get_logs_for_trace(&self, trace_id: &str) -> Vec { + self.inner + .logs + .read() + .iter() + .filter(|log| log.trace_id.as_deref() == Some(trace_id)) + .cloned() + .collect() + } + + pub fn get_services(&self) -> Vec { + self.inner + .services + .iter() + .map(|entry| entry.value().clone()) + .collect() + } + + pub fn get_service(&self, name: &str) -> Option { + self.inner.services.get(name).map(|s| s.clone()) + } +} + +impl Default for Store { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::{SpanKind, SpanStatus}; + use chrono::Utc; + use std::collections::HashMap; + + fn make_span(trace_id: &str, span_id: &str, service: &str, is_error: bool) -> Span { + Span { + trace_id: trace_id.to_string(), + span_id: span_id.to_string(), + parent_span_id: None, + name: "test-span".to_string(), + service_name: service.to_string(), + kind: SpanKind::Server, + start_time: Utc::now(), + end_time: Utc::now(), + duration_ms: 10.0, + status: SpanStatus { + code: if is_error { + StatusCode::Error + } else { + StatusCode::Ok + }, + message: None, + }, + attributes: HashMap::new(), + } + } + + fn make_log(service: &str, severity: Severity, trace_id: Option<&str>) -> LogRecord { + LogRecord { + timestamp: Utc::now(), + service_name: service.to_string(), + severity, + body: "test log message".to_string(), + trace_id: trace_id.map(|s| s.to_string()), + span_id: None, + attributes: HashMap::new(), + } + } + + #[test] + fn test_insert_and_retrieve_span() { + let store = Store::new(); + let span = make_span("trace-1", "span-1", "api-gateway", false); + + store.insert_span(span.clone()); + + let spans = store.get_recent_spans(10); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].trace_id, "trace-1"); + } + + #[test] + fn test_insert_and_retrieve_log() { + let store = Store::new(); + let log = make_log("api-gateway", Severity::Info, None); + + store.insert_log(log); + + let logs = store.get_recent_logs(10); + assert_eq!(logs.len(), 1); + assert_eq!(logs[0].service_name, "api-gateway"); + } + + #[test] + fn test_service_auto_discovery() { + let store = Store::new(); + + store.insert_span(make_span("t1", "s1", "service-a", false)); + store.insert_span(make_span("t2", "s2", "service-b", false)); + store.insert_log(make_log("service-a", Severity::Info, None)); + + let services = store.get_services(); + assert_eq!(services.len(), 2); + + let svc_a = store.get_service("service-a").unwrap(); + assert_eq!(svc_a.span_count, 1); + assert_eq!(svc_a.log_count, 1); + } + + #[test] + fn test_error_counting() { + let store = Store::new(); + + store.insert_span(make_span("t1", "s1", "api", false)); + store.insert_span(make_span("t2", "s2", "api", true)); // error + store.insert_log(make_log("api", Severity::Error, None)); // error + + let svc = store.get_service("api").unwrap(); + assert_eq!(svc.span_count, 2); + assert_eq!(svc.error_count, 2); // 1 span error + 1 log error + } + + #[test] + fn test_trace_lookup() { + let store = Store::new(); + + store.insert_span(make_span("trace-abc", "span-1", "svc", false)); + store.insert_span(make_span("trace-abc", "span-2", "svc", false)); + store.insert_span(make_span("trace-xyz", "span-3", "svc", false)); + + let trace = store.get_trace("trace-abc").unwrap(); + assert_eq!(trace.len(), 2); + + assert!(store.get_trace("nonexistent").is_none()); + } + + #[test] + fn test_logs_for_trace() { + let store = Store::new(); + + store.insert_log(make_log("svc", Severity::Info, Some("trace-123"))); + store.insert_log(make_log("svc", Severity::Warn, Some("trace-123"))); + store.insert_log(make_log("svc", Severity::Info, Some("trace-456"))); + store.insert_log(make_log("svc", Severity::Info, None)); + + let logs = store.get_logs_for_trace("trace-123"); + assert_eq!(logs.len(), 2); + } + + #[test] + fn test_recent_order_newest_first() { + let store = Store::new(); + + store.insert_span(make_span("t1", "first", "svc", false)); + store.insert_span(make_span("t2", "second", "svc", false)); + store.insert_span(make_span("t3", "third", "svc", false)); + + let spans = store.get_recent_spans(10); + assert_eq!(spans[0].span_id, "third"); + assert_eq!(spans[1].span_id, "second"); + assert_eq!(spans[2].span_id, "first"); + } + + #[test] + fn test_limit_parameter() { + let store = Store::new(); + + for i in 0..10 { + store.insert_span(make_span(&format!("t{}", i), &format!("s{}", i), "svc", false)); + } + + let spans = store.get_recent_spans(3); + assert_eq!(spans.len(), 3); + } +} diff --git a/ui/dist/.gitkeep b/ui/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ui/dist/index.html b/ui/dist/index.html new file mode 100644 index 0000000..77da0d6 --- /dev/null +++ b/ui/dist/index.html @@ -0,0 +1,668 @@ + + + + + + Hydra + + + +
+
+ +
+
+
+ +
+
+ + + + +
+ +
+ +
+
+
+
Trace Details
+
+
+ +
+
+

Spans (Waterfall)

+
+
+
+

Related Logs

+
+
+
+
+ + + + diff --git a/ui/fallback.html b/ui/fallback.html new file mode 100644 index 0000000..02a6b28 --- /dev/null +++ b/ui/fallback.html @@ -0,0 +1,68 @@ + + + + + + Hydra - Local Dev Observability + + + +
+

🐉 Hydra

+

Local-first dev observability. Zero config. Just works.

+ +
+

✓ Server Running

+

OTLP endpoint ready at:

+
grpc://localhost:4317
+

Point your OpenTelemetry SDK here and start seeing data.

+
+ +
+

API Endpoints

+

/api/services - List discovered services

+

/api/spans - Recent spans

+

/api/logs - Recent logs

+

/health - Health check

+
+ +

+ Full UI coming soon. For now, use the API endpoints above. +

+
+ +