A complete five-minute path: create an environment, grab the ingest token, push your first line with curl, verify in the console, then move to Fluent Bit and Kubernetes for production, with copy-paste commands.
Ingestion is the first hurdle of any logging platform, and it's where most teams stall — agent configs, index templates, auth between daemons. JUJING OBSERVE keeps the whole thing deliberately simple: one token per environment, POST JSON Lines, done. Here's the full path from zero to your first record, under five minutes.
After logging in, create an environment (say, prod) under Environments. The system generates a dedicated ingest token for it. Tokens are separate from your login password and scoped one-per-environment; a tenant can have multiple environments (prod / uat / dev), and switching environments in the top bar filters search, monitoring, and export accordingly.
This separation matters: the token your collector ships with can be revoked without touching anyone's login, and each environment's data stays addressable on its own. Copy the token — it's the credential for every command below.
The ingest endpoint is POST /api/ob/logs/jsonline. Send X-Ingest-Token in the header and JSON Lines (NDJSON) in the body, one object per line. Verify with curl:
export OB_API=https://ob.jjhub.cn
export OB_INGEST_TOKEN='your_token'
echo '{"log.file.path":"/var/log/app/service.log","content":"2026-07-26 17:44:46.159 ERROR [t1] [main] demo.App:run():1 : hi","k8s_label_ob-name":"demo"}' | curl -X POST "$OB_API/api/ob/logs/jsonline" -H 'Content-Type: application/x-ndjson' -H "X-Ingest-Token: $OB_INGEST_TOKEN" --data-binary @-
A response of {"code":0,"message":"success","data":null} means it worked. A few notes that save debugging time:
log.file.path ends with service.log it's treated as an application log; tracing.log routes to trace data. Get this suffix wrong and records land in the wrong view.content should use Log4j2-style text (time, level, [traceId], thread name) so the platform can parse the fields instead of storing one opaque blob.k8s_label_ob-company / k8s_label_ob-project / k8s_label_ob-name to aggregate by company, project, and service — cheap to set once, painful to retrofit.Back in the console, switch to the environment you created and open the Service page — the ERROR line should be there. Filter with a SQL-like condition: level="ERROR" and serviceName="demo". Timeline, record details, and traceId drill-down are all on this page. At this point your ingest path is fully working, and everything after is just plumbing to make it durable at scale.
Don't run raw curl in production. Use Fluent Bit with tail + HTTP output:
[INPUT]
Name tail
Path /var/log/app/service.log
Tag service
[OUTPUT]
Name http
Match *
Host ob.jjhub.cn
Port 443
URI /api/ob/logs/jsonline
Format json_lines
Header X-Ingest-Token ${OB_INGEST_TOKEN}
On Kubernetes, label your pods ob-company / ob-project / ob-name and collect via EmptyDir + sidecar or a DaemonSet. Spring Boot apps write service.log / tracing.log to disk and have an agent push them; low-volume services can POST directly over HTTP. Once connected, create an alert rule right away (for example, more than 20 ERROR lines in 5 minutes) so the platform notifies you before the problem grows — logs only help if someone looks, and an alert is what makes that reliable.
If your first record doesn't show up, work through three things in order. First, confirm you're viewing the same environment the token belongs to — data is scoped per environment, and it's easy to be staring at dev while your curl hit prod. Second, check the log.file.path suffix: service.log goes to the Service view, tracing.log to Tracing; a mismatched suffix drops records into a view you aren't looking at. Third, verify the token is present in the header and wasn't mangled by shell quoting — a missing or truncated token returns 401 with code=-1.
If your services already emit OpenTelemetry, you can skip the custom line format entirely. JUJING OBSERVE accepts OTLP/HTTP JSON at POST /api/otlp/v1/logs and /api/otlp/v1/traces with the same X-Ingest-Token auth, so an existing OTLP collector can forward both signals by changing a single endpoint rather than re-encoding every field by hand. Whichever path you pick, do the five-minute curl test first — it isolates the platform from your collector, so when something breaks later you already know which half of the pipeline to blame.