9.1 KiB
OpenObserve — project rules
Engineering guidelines
- .agents/engineering-guidelines.md governs judgment: task tiers, what counts as a hack / over-engineering / over-complication, TDD and the review loop. Read it before any change bigger than a one-cause patch; reviewers apply its §2 tests. On anything mechanical (ordering, comments, lint thresholds, schema, PR rules) this file wins.
Build & workflow
- Build with
cargo build(debug). Never use--releaseunless explicitly asked. - Skip UI build steps (
npm run build) when working on backend tasks. .envin the project root overrides process env; check it before running the server.- Before finishing a task: run
cargo fmt --alland make the CI clippy command pass:cargo clippy --workspace --all-targets -- -W clippy::too_many_lines -W clippy::cognitive_complexity -W clippy::excessive_nesting -D warnings.
Frontend (web/)
- After every change under
web/, runnpm run format:checkfromweb/before considering the task done. If it flags a file, fix it withnpx prettier --write <file>(not a full-repo--write) and re-runformat:checkto confirm it's clean. - New UI features fetch search results over the HTTP streaming endpoints, not
the one-shot ones. Logs and other SQL searches use
POST /api/{org}/_search_stream, or_search_multi_streamfor several streams. Traces useGET /api/{org}/{stream}/traces/latest_stream. Call them throughuseHttpStreaminginweb/src/composables/useStreamingSearch.ts, which handles chunking, cancellation and thetrace_id. Do not add new UI callers of_search,_search_multiortraces/latest. Wherever a streaming endpoint is supported for a call, the UI uses it, in existing code as well as new code. The one-shot endpoints are for the public API and for the few UI calls that have no streaming equivalent yet.
Rust code organization
Item order inside a file/module, top to bottom (clippy's default grouping):
mod/pub mod declarations → use imports → macros → const/static → types (struct/enum/trait/type), each immediately followed by its own impl blocks (inherent impl first, then trait impls) → functions.
-
A type and its
implblocks stay together. Do not collect all types at the top and allimplblocks below them; a reader must see a struct's methods right under its fields. Types that only serve another type (private helper structs/enums) go after the type that uses them, again followed by their own impls. No lint enforces adjacency, review does. -
Function length, cognitive complexity, and nesting depth are capped by the thresholds in clippy.toml (set just above the worst existing code). They only ratchet down — if CI flags your function, split it instead of raising the threshold.
-
Never insert a function above the file's constants or type definitions.
-
Keep
puband private functions grouped. Do not drop a private helper into the middle of the pub API block, or a pub fn into a run of private helpers. Place new functions next to related functions within the correct group. -
#[cfg(test)] mod testsis always the LAST item in a file. CI enforces this (clippyitems_after_test_moduleruns under--all-targets -- -D warnings). -
The module-level order above matches clippy's
arbitrary_source_item_orderinglint with thesource-item-ordering = ["module"]config in clippy.toml. The lint is opt-in (too much pre-existing debt to enable repo-wide); hold the code you add to this order even though nothing enforces it yet.
Comments
STRICT, and enforced in review — a comment that breaks these is deleted, not reworded:
- One line, or none. A comment is at most a single line. No multi-line comment blocks in code (the license header is the only exception). If the point needs more than one line, it does not belong in a comment.
- Only logical comments — explain WHY. The single allowed reason to write a comment is to state a non-obvious constraint, invariant, or gotcha the code itself cannot express. If a comment is not explaining logic, do not write it; no comment is better than a filler one.
- Banned outright (remove on sight): styling/visual narration (layout,
spacing,
px, "matches the header height"), change/modification notes ("now we...", "changed to...", "was X"), and narration that restates what the code plainly does. //// JSDoc doc comments on public APIs: one summary sentence, and only when the behaviour is non-obvious; never to restate the signature.
Database schema (migrations / SeaORM entities)
Applies to every NEW table and column (SQLite and PostgreSQL alike):
-
MySQL is not a supported meta store. Support was removed in #10522 (
src/infra/src/db/mysql.rsis gone). Do not add MySQL branches, MySQL-only migrations, MySQL feature flags or MySQL test matrices. Remainingmysqlmentions in the tree belong to the database monitoring feature, which watches customers' MySQL servers, not to the meta store. -
No
json/jsonbcolumns (sea-orm.json()/.json_binary()). JSON is stored in a.text()column holding the serialized string; Rust (serde_json) parses it. Never push JSON operators or extraction into SQL. -
No
timestamp/datetime/datecolumns (sea-orm.timestamp(),.date_time(),.date()). A time field is a Unix timestamp in MICROSECONDS stored as.big_integer()(Rusti64), the same unit_timestampuses everywhere else. -
Existing columns that break these rules are legacy; do not add new ones next to them, and do not migrate them as part of an unrelated change.
Feature-owned internal streams
- Any new feature that uses OpenObserve itself to ingest and query its own
operational or result data must name its feature-owned streams with the
_o2_prefix. Existing examples:_o2_dbm_server(DBM_SERVER_STREAMinsrc/config/src/meta/db_monitoring.rs),_o2_ai_chat_events(AI_CHAT_EVENTS_STREAMinsrc/config/src/meta/self_reporting/ai_chat.rs) and_o2_alert_calibration_digests(DIGEST_STREAMinsrc/core/src/alerts/alert_calibration/sink.rs). Apply this to every stream the feature creates, including execution-level and step-level results. - Reuse shared stream-name constants within each code layer. Keep writers, readers, runner configuration, ingestion grants and internal-stream classification consistent. Verify that the actual ingestion path accepts authorized writes to the reserved prefix.
- Writers into feature-owned streams must authenticate with an org-based
ingestion token from the
org_ingestion_tokenstable. Do not use a user passcode, a RUM token, a service-account login or a root credential. Create or look up the token per org when the feature is enabled, and scope it to that org. Rotate and revoke it together with the other ingestion tokens of the org. - Before renaming an existing stream, assess existing customers, saved queries, dashboards, alerts, historical analytics, in-flight jobs and older runners. State the history and rollout impact explicitly and choose a compatibility plan. When continuous history is required, read legacy and new streams during the transition and retain legacy write permissions until old writers drain. Preserve existing data under its retention policy unless deletion or migration is explicitly requested.
- This convention applies to feature-owned internal data, not customer-chosen
stream names or externally standardized names such as
traces_service_graph_*. Do not rename shared streams as an incidental change. Internal-stream naming/classification is not an access-control or billing rule.
PR conventions
- CI runs on a PR only while it carries the
ready-for-cilabel and is not a draft (no label or draft = nothing runs exceptci-gate, and no red checks). Add the label when you want the run, not when you open the PR; clicking "Merge when ready" adds it automatically if it is missing. Which suites run is decided by path detection, never by a label. The merge queue only compiles and unit-tests the merged code, so the labelled PR run is the one that has to be green. - PR titles starting with
feat:requireDesign at: #xxxas the FIRST line of the description (enforced by.github/workflows/feat-design-checker.yml);fix:/chore:/test:PRs skip the check.
Enterprise-gated code
- Default features do NOT compile
#[cfg(feature = "enterprise")]code, so a green local check proves nothing about it, and--features enterprisedoes not work in this repo (the localsrc/enterprise/*stubs don't provide it). With access to the o2-enterprise repo, verify by swapping in itsCargo.toml.openobserveand runningcargo check --all-targets, then restoreCargo.toml(never commit the swap). Without access, say so in the PR. - A change needing paired PRs in openobserve and o2-enterprise must use the SAME branch name in both repos — CI checks out the paired side by branch name.
- When changing this repo's root
Cargo.toml, mirror the change intoCargo.toml.openobservein the o2-enterprise repo.