Files
ImageApi/.specify/memory/constitution.md
T
Cameron CordesandClaude Opus 5.5 3b3e6bdb39 feat(otel): export logs through the tracing pipeline, correlated with traces (#147)
Logs were a separate pipeline: env_logger in debug (never exported) and
opentelemetry-appender-log in release (exported without trace context,
so SigNoz could not link logs to traces).

- tracing-subscriber `tracing-log` feature: try_init installs LogTracer,
  so log:: records become tracing events: one console format, and
  RUST_LOG per-module directives still apply to them.
- opentelemetry-appender-tracing exports events as OTel log records when
  OTLP_OTLS_ENDPOINT is set (any build), stamped with the enclosing
  span's trace_id/span_id. A per-layer filter drops the exporter's own
  crates.
- The OTel span layer keeps only WARN/ERROR events as span events; the
  info/debug copies duplicated the correlated logs.
- Remove env_logger init in the server, init_logs, and the unused
  opentelemetry-appender-log / opentelemetry-stdout deps.
- tests/otel_log_correlation.rs pins correlation (native + bridged),
  the span-event filter, and per-module filtering of bridged records.
- Constitution 1.2.0 (Principle V logging rule), CLAUDE.md, README,
  .env.example.

Closes #147

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:56:10 -04:00

182 lines
8.0 KiB
Markdown

<!--
Sync Impact Report
==================
Version change: 1.1.0 -> 1.2.0
Bump rationale: MINOR. Principle V's logging rule is materially changed:
logs are no longer a separate, build-profile-dependent pipeline; they
share the tracing subscriber and export correlated with traces in any
build (#147). No principle is removed or redefined in intent.
Modified principles:
- V. Observability: logging bullet rewritten; span events limited to
WARN/ERROR
Also updated:
- Technology Stack & Constraints: Tracing line
Added sections: N/A
Removed sections: N/A
Templates requiring updates:
- .specify/templates/plan-template.md — ✅ no changes needed (generic Constitution Check)
- .specify/templates/spec-template.md — ✅ no changes needed
- .specify/templates/tasks-template.md — ✅ no changes needed
- .specify/templates/checklist-template.md — ✅ no changes needed
- .specify/templates/agent-file-template.md — ✅ no changes needed
Runtime guidance:
- CLAUDE.md "Tracing" — ✅ logs bullet added; dependency list updated
- README.md — ✅ RUST_LOG default + OTLP_OTLS_ENDPOINT now cover logs
- .env.example — ✅ telemetry header covers logs
Follow-up TODOs: migrate `log::` macros to `tracing::` (bridged records
export with target "log"; module survives only in code.namespace)
-->
# ImageApi Constitution
## Core Principles
### I. Layered Architecture
All features MUST follow the established layered architecture:
- **HTTP Layer** (`main.rs`, feature modules): Route handlers, request
parsing, response formatting. No direct database access.
- **Service Layer** (`files.rs`, `exif.rs`, `memories.rs`, etc.): Business
logic. No HTTP-specific types.
- **DAO Layer** (`database/` trait definitions): Trait-based data access
contracts. Every DAO MUST be defined as a trait to enable mock
implementations for testing.
- **Database Layer** (Diesel ORM, `schema.rs`): Concrete `Sqlite*Dao`
implementations. Every query runs inside a `tracing` span via
`otel::trace_db_call`.
New features MUST NOT bypass layers (e.g., HTTP handlers MUST NOT
execute raw SQL). Actix actors are permitted for long-running async
work (video processing, file watching) but MUST interact with the
DAO layer through the established trait interfaces.
### II. Path Safety (NON-NEGOTIABLE)
All user-supplied file paths MUST be validated against `BASE_PATH`
using `is_valid_full_path()` before any filesystem operation. This
prevents directory traversal attacks.
- Paths stored in the database MUST be relative to `BASE_PATH`.
- Paths passed to external tools (ffmpeg, image processing) MUST be
fully resolved absolute paths.
- Extension detection MUST use the centralized helpers in
`file_types.rs` (case-insensitive). Manual string matching on
extensions is prohibited.
### III. Trait-Based Testability
All data access MUST go through trait-based DAOs so that every
handler and service can be tested with mock implementations.
- Each DAO trait MUST be defined in `src/database/` and require
`Sync + Send`.
- Mock DAOs for testing MUST live in `src/testhelpers.rs`.
- Integration tests against real SQLite MUST use in-memory databases
via `in_memory_db_connection()` from `database::test`.
- Handler tests MUST use `actix_web::test` utilities with JWT token
injection (using `Claims::valid_user()` and the `test_key` secret).
- New DAO implementations MUST include a `#[cfg(test)]` constructor
(e.g., `from_connection`) accepting an injected connection.
### IV. Environment-Driven Configuration
Server behavior MUST be controlled through environment variables
loaded from `.env` files. Hard-coded paths, URLs, or secrets are
prohibited.
- Required variables MUST call `.expect()` with a clear message at
startup so misconfiguration fails fast.
- Optional variables MUST use `.unwrap_or_else()` with sensible
defaults and be documented in `README.md`.
- Any new environment variable MUST be added to the README
environment section before the feature is considered complete.
### V. Observability
All database operations and HTTP handlers MUST be instrumented with
`tracing` spans, exported through `tracing-opentelemetry`: DAO bodies via
`otel::trace_db_call`, and handlers / feature functions via
`#[tracing::instrument]`. The per-request root span comes from the
`tracing-actix-web` middleware, which also honors an incoming W3C
`traceparent`.
- Spans are produced in debug and release builds alike. OTLP export is
enabled by `OTLP_OTLS_ENDPOINT`, not by build profile.
- Code MUST NOT thread an OpenTelemetry `Context` through call chains.
Spans parent on the ambient span.
- Every `tokio::spawn` or cross-thread fan-out (rayon, per-pass runtimes)
MUST carry its parent span: `.instrument(span)` or
`info_span!(parent: &span, ...)`. Tracing context is thread-local, and
an uncarried spawn orphans its spans onto a fresh trace.
- `RUST_LOG` gates span export as well as console output (one `EnvFilter`).
Deployments MUST run at `info` or `debug`.
- Logs share the tracing subscriber: `log::` records are bridged into
`tracing`, so both print in one console format. With
`OTLP_OTLS_ENDPOINT` set (any build), events export as OTel log records
carrying the enclosing span's trace_id / span_id. Only WARN and ERROR
events are also recorded on spans as span events.
- Prometheus metrics (`imageserver_image_total`,
`imageserver_video_total`) MUST be maintained for key counters.
- Errors MUST be logged at `error!` level with sufficient context
for debugging without reproducing the issue.
**Rationale:** span parenting that relies on hand-threaded contexts drifted
into orphaned traces (the pre-migration baseline, #137). Ambient
parenting, plus an explicit carry at each spawn boundary, keeps a request or
job in one trace by construction.
## Technology Stack & Constraints
- **Language**: Rust (stable toolchain, Cargo build system)
- **HTTP Framework**: Actix-web 4
- **ORM**: Diesel 2.2 with SQLite backend
- **Auth**: JWT (HS256) via `jsonwebtoken` crate, bcrypt password
hashing
- **Video Processing**: ffmpeg/ffprobe (CLI, must be on PATH)
- **Image Processing**: `image` crate for thumbnails, `kamadak-exif`
for EXIF extraction
- **Tracing**: `tracing` + `tracing-opentelemetry` (every build), OTLP
export when `OTLP_OTLS_ENDPOINT` is set; logs through the same
subscriber via `opentelemetry-appender-tracing` (`log::` bridged)
- **Testing**: `cargo test`, `actix_web::test`, in-memory SQLite
External dependencies (ffmpeg, Ollama) are optional runtime
requirements. The server MUST start and serve core functionality
(images, thumbnails, tags) without them. Features that depend on
optional services MUST degrade gracefully with logged warnings,
not panics.
## Development Workflow
- `cargo fmt` MUST pass before committing.
- `cargo clippy` warnings MUST be resolved or explicitly suppressed
with a justification comment.
- `cargo test` MUST pass with all tests green before merging to
master.
- Database schema changes MUST use Diesel migrations
(`diesel migration generate`), with hand-written SQL in `up.sql`
and `down.sql`, followed by `diesel print-schema` to regenerate
`schema.rs`.
- Features MUST be developed on named branches
(`###-feature-name`) and merged to master via pull request.
- File uploads MUST preserve existing files (append timestamp on
conflict, never overwrite).
## Governance
This constitution defines the non-negotiable architectural and
development standards for the ImageApi project. All code changes
MUST comply with these principles.
- **Amendments**: Any change to this constitution MUST be documented
with a version bump, rationale, and updated Sync Impact Report.
- **Versioning**: MAJOR for principle removals/redefinitions, MINOR
for new principles or material expansions, PATCH for wording
clarifications.
- **Compliance**: Pull request reviews SHOULD verify adherence to
these principles. The CLAUDE.md file provides runtime development
guidance and MUST remain consistent with this constitution.
**Version**: 1.2.0 | **Ratified**: 2026-02-26 | **Last Amended**: 2026-09-29