Logging
Send application logs to JuhJuh using simple JSON or OpenTelemetry format. Search, filter, and correlate logs with traces. Structured logging with custom attributes and automatic level detection.
Logs are the most direct signal your application produces. A request failed, a payment succeeded, a job timed out. JuhJuh ingests your logs, indexes them by service, level, and timestamp, and links them to distributed traces so you can follow a single request from log entry to full execution path.
Sending logs¶
Send logs to the ingestion endpoint:
POST /api/ingest/logs/
Authorization: Bearer jjk_YOUR_API_KEY
Content-Type: application/json
The endpoint auto-detects two formats from the request body. If the top-level key is resourceLogs, it parses OTLP. If the top-level key is logs, it parses simple JSON. No configuration flag needed.
Simple JSON format¶
The simplest way to send logs. No SDK required.
{
"logs": [
{
"message": "Order #4521 payment processed",
"level": "info",
"service": "payment-service",
"timestamp": "2026-04-09T14:30:00Z",
"attributes": {
"order_id": "4521",
"amount": 59.99,
"currency": "USD"
}
}
]
}
| Field | Required | Default | Description |
|---|---|---|---|
message |
Yes | The log message text | |
level |
No | info |
One of: debug, info, warning, error, critical |
service |
No | unknown |
The service name that produced this log |
timestamp |
No | Server time | ISO 8601 timestamp. If omitted, JuhJuh uses the time the request arrives |
attributes |
No | {} |
Key-value pairs for structured data. Any JSON-serializable values |
OTLP format¶
If your application uses an OpenTelemetry SDK, the SDK sends logs in OTLP format automatically. The structure follows the OpenTelemetry Log Data Model:
{
"resourceLogs": [
{
"resource": {
"attributes": [
{ "key": "service.name", "value": { "stringValue": "payment-service" } }
]
},
"scopeLogs": [
{
"logRecords": [
{
"timeUnixNano": "1712672400000000000",
"severityText": "INFO",
"body": { "stringValue": "Order #4521 payment processed" },
"attributes": [
{ "key": "order_id", "value": { "stringValue": "4521" } }
]
}
]
}
]
}
]
}
JuhJuh extracts the service name from resource.attributes, maps severityText to the log level, reads the message from body.stringValue, and merges attributes from both the resource and log record levels.
Log levels¶
Use log levels consistently so you can filter effectively:
| Level | When to use |
|---|---|
debug |
Detailed diagnostic information. Disabled in production by most frameworks, but useful when sent during active debugging |
info |
Normal operations. User actions, request completions, job results |
warning |
Something unexpected that did not cause a failure. Deprecated API usage, slow queries, retried operations |
error |
A failure that affected a specific operation. Failed payment, broken external API call, unhandled exception |
critical |
System-level failure. Database unreachable, out of memory, service cannot start |
Structured logging¶
Plain text log messages are hard to search at scale. Structured logging adds key-value attributes to every log entry so you can filter on specific fields.
Why it matters¶
Compare these two approaches:
Unstructured:
"User usr_12345 failed to login from 192.168.1.1 after 3 attempts"
Structured:
{
"message": "Login failed after maximum attempts",
"level": "warning",
"service": "auth-service",
"attributes": {
"user_id": "usr_12345",
"ip": "192.168.1.1",
"attempts": 3
}
}
The structured version lets you query all failed logins for a specific user, filter by IP range, or count entries where attempts exceeded a threshold. The unstructured version requires regex parsing across millions of log lines.
Recommended attributes¶
These common attributes make logs more useful across services:
| Attribute | Example | Purpose |
|---|---|---|
trace_id |
"abc123def456" |
Links this log to a distributed trace for cross-signal correlation |
user_id |
"usr_12345" |
Associates the log with a specific user |
request_id |
"req_abc123" |
Groups all logs from a single HTTP request |
environment |
"production" |
Distinguishes production logs from staging or development |
version |
"2.4.1" |
Identifies which release of your service produced this log |
error.type |
"TimeoutError" |
Categorizes errors for aggregation |
Trace-to-log correlation¶
Include a trace_id attribute in your logs to connect them with distributed traces. When you view a trace in JuhJuh, all logs that share the same trace ID appear alongside the trace spans.
If you use an OpenTelemetry SDK for both tracing and logging, trace IDs are injected into logs automatically.
Batching¶
Send multiple log entries in a single request to reduce HTTP overhead:
{
"logs": [
{ "message": "Request received", "level": "info", "service": "api" },
{ "message": "Database query completed", "level": "debug", "service": "api" },
{ "message": "Response sent", "level": "info", "service": "api" }
]
}
| Batch size | Behavior |
|---|---|
| 1 to 50 entries | Processed synchronously, returns 200 |
| 51+ entries | Accepted with 202, processed asynchronously in the background |
Async processing retries up to 3 times with 10-second delays if a transient error occurs.
Searching logs¶
The log viewer in your dashboard provides search and filtering across all ingested logs.
Filters¶
| Filter | What it does |
|---|---|
| Service | Show logs from a specific service only |
| Level | Filter by log level (debug, info, warning, error, critical) |
| Time range | Narrow to a specific time window |
| Search | Full-text search across log messages |
Searching by trace¶
If you included a trace_id attribute in your logs, you can search for all logs associated with a specific trace. This is the fastest way to reconstruct what happened during a single request across multiple services.
Infrastructure logs¶
If your application runs on JuhJuh-managed VMs, container logs are collected automatically without any SDK or configuration. JuhJuh streams container output in real time and stores it alongside your application logs.
Infrastructure logs appear in the same log viewer as push-based logs. Filter by service name to distinguish between your application logs and container output.
Data retention¶
Logs are retained for 14 days by default. After the retention period, log entries are permanently deleted in daily cleanup cycles. Plan your log levels accordingly: sending everything at debug level fills your retention window faster than info and above.
Example: Python logging integration¶
Send structured logs from a Python application without an OpenTelemetry SDK:
import logging
import json
import requests
class JuhJuhHandler(logging.Handler):
def __init__(self, endpoint, api_key, service_name):
super().__init__()
self.endpoint = endpoint
self.api_key = api_key
self.service_name = service_name
self.buffer = []
def emit(self, record):
entry = {
"message": record.getMessage(),
"level": record.levelname.lower(),
"service": self.service_name,
"attributes": {
"logger": record.name,
"module": record.module,
},
}
self.buffer.append(entry)
if len(self.buffer) >= 50:
self.flush()
def flush(self):
if not self.buffer:
return
requests.post(
self.endpoint,
json={"logs": self.buffer},
headers={"Authorization": f"Bearer {self.api_key}"},
)
self.buffer = []
handler = JuhJuhHandler(
endpoint="https://your-org.juhjuh.com/api/ingest/logs/",
api_key="jjk_YOUR_API_KEY",
service_name="my-service",
)
logging.getLogger().addHandler(handler)
This handler buffers log entries and flushes them in batches of 50. In production, add error handling around the HTTP request and flush on application shutdown.
Related¶
- Observability for an overview of all observability signals
- Instrumentation for SDK setup and API key management
- Tracing for distributed tracing and trace-to-log correlation
- Metrics for time-series data and performance monitoring
- Alerts for notifications when error logs spike
- VMs for JuhJuh-managed infrastructure with automatic log collection