Observo
Documentation
Home Dashboard →
Documentation

Observo Documentation

Observo is a self-hosted centralized log management platform. It ingests logs in real time via a REST API or Python handler, surfaces them in a searchable dashboard, and alerts you when something goes wrong.

Open & self-hosted Observo runs on your own infrastructure. Your logs never leave your servers. Deploy with Docker in under 5 minutes.

This documentation covers everything from initial setup to advanced configuration. Use the sidebar to jump to any section.

Getting Started

Get from zero to your first log entry in under 5 minutes.

1

Create an account

Log in to your Observo instance and create your first user via the admin panel or the login page.

2

Create a project

From the dashboard, click New Project. Give it a name and select an environment (development, staging, or production).

3

Copy your credentials

From the project detail page, copy your Project ID and API Key. You'll need these to authenticate log submissions.

4

Send your first log

Install the Python handler or make a curl request — and watch your logs appear in real time.

bash — quick test
curl -X POST https://observo-log.vendlyghana.space/api/v1/ingest/ \
  -H "X-Project-ID: YOUR_PROJECT_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"level": "INFO", "message": "Hello, Observo!"}'

# 201 Created → {"id": "log_01j...", "created": "2026-03-16T..."}

Core Concepts

Understanding the key building blocks of Observo.

Projects

A Project is the top-level container for logs. Each project has a unique project_id and one or more api_keys. Logs are always scoped to a project. Use separate projects for different applications or environments.

Log Entries

A Log Entry contains a level, message, logger name, timestamp, and optional extra metadata. Observo stores all fields and makes them fully searchable.

Alert Rules

Alert Rules define conditions on your logs (e.g., "ERROR count > 5 in 1 minute") and fire notifications when triggered. Notifications can go to email, Slack, or a custom webhook.

Python Handler

The observo-handler package provides a drop-in Python logging handler. It ships logs in the background via a thread-safe queue with batching and fail-safe delivery.

Installation

bash
pip install observo-handler

Basic Usage

python
import logging
from observo_handler import ObservoHandler

# Configure handler
handler = ObservoHandler(
    project_id="your-project-id",
    api_key="obs_live_xxxxxxxxxxxx",
    observo_url="https://observo-log.vendlyghana.space/api/v1/ingest/",
    batch_size=10,          # flush after 10 logs
    flush_interval=5,     # flush every 5 seconds
)

# Attach to any logger
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)

# Use standard logging — no API calls needed in your code
logger.info("Application started")
logger.warning("Disk usage above 80%%", extra={"disk_pct": 82})
logger.error("Unhandled exception", exc_info=True)

Handler Configuration

Parameter Type Default Description
project_idstrrequiredYour project's unique ID
api_keystrrequiredYour project's API key
observo_urlstrrequiredFull URL to the ingest endpoint
batch_sizeint10Number of logs before forcing a flush
flush_intervalint5Seconds between automatic flushes
timeoutint5HTTP request timeout in seconds
max_retriesint3Retry attempts on network failure
Fail-safe design If the Observo server is unreachable, the handler logs errors to stderr and continues. Your application will never crash due to a logging failure.

Django Integration

Integrate Observo into a Django project using the standard LOGGING configuration and optional request ID middleware.

LOGGING Settings

python — settings.py
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
        "observo": {
            "class": "observo_handler.ObservoHandler",
            "project_id": env("OBSERVO_PROJECT_ID"),
            "api_key": env("OBSERVO_API_KEY"),
            "observo_url": env("OBSERVO_URL"),
            "batch_size": 20,
            "flush_interval": 10,
        },
    },
    "root": {
        "handlers": ["console", "observo"],
        "level": "INFO",
    },
}

Request ID Middleware

Add RequestIDMiddleware to inject a unique request_id into every log entry, making it easy to trace all logs for a given HTTP request.

python — settings.py
MIDDLEWARE = [
    "observo_handler.middleware.RequestIDMiddleware",  # First!
    "django.middleware.security.SecurityMiddleware",
    # ... rest of your middleware
]

Flask Integration

python — app.py
import logging
from flask import Flask
from observo_handler import ObservoHandler

app = Flask(__name__)

# Attach to Flask's app logger
handler = ObservoHandler(
    project_id="my-flask-app",
    api_key="obs_live_xxxxxxxxxxxx",
    observo_url="https://observo-log.vendlyghana.space/api/v1/ingest/",
)
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)

@app.route("/")
def index():
    app.logger.info("Home page visited")
    return "Hello!"

API Reference

Complete reference for the Observo REST API. Base URL: https://observo-log.vendlyghana.space/api/v1/

Endpoints

POST /api/v1/ingest/
Submit one or more log entries
GET /api/v1/logs/
List log entries with filtering and pagination
GET /api/v1/logs/{id}/
Retrieve a single log entry
GET /api/v1/projects/
List your projects
GET /health/
Health check — returns service status

Ingest a Log Entry

http — POST /api/v1/ingest/
POST /api/v1/ingest/
X-Project-ID: your-project-id
Authorization: Bearer your-api-key
Content-Type: application/json

{
  "level": "ERROR",
  "message": "Payment failed for order #1234",
  "logger": "payments.stripe",
  "timestamp": "2026-03-16T12:41:09.000Z",
  "extra": {
    "user_id": 42,
    "order_id": 1234,
    "amount": 4999
  }
}

HTTP/1.1 201 Created
{
  "id": "log_01j9xk...",
  "created": "2026-03-16T12:41:09.123Z"
}

Log Entry Fields

FieldTypeRequiredDescription
levelstringYesDEBUG, INFO, WARNING, ERROR, or CRITICAL
messagestringYesThe log message text
loggerstringNoLogger name (e.g. "myapp.views")
timestampISO 8601NoDefaults to server receive time
extraobjectNoArbitrary JSON metadata
request_idstringNoFor tracing related log entries
user_idstring/intNoAssociated user identifier

Authentication

Every API request must include two headers: your Project ID and API Key. Both are available from your project's settings page.

http headers
X-Project-ID: your-project-id
Authorization: Bearer your-api-key
Keep your API key secret Never commit API keys to version control. Use environment variables. You can regenerate your API key at any time from the project settings page.

Log Levels

LevelNumericWhen to use
DEBUG10Detailed diagnostic information for development
INFO20Normal application events (requests, transactions)
WARNING30Something unexpected, but the app is still working
ERROR40A failure that prevented a specific operation
CRITICAL50A serious failure — the app may be unable to continue

Logging Best Practices

The difference between a useful log and a useless one is context. A log that says api_request tells you something happened. A log that says api_request POST /api/v1/checkout 422 89ms user=u_8821 tells you what to fix.

The most common mistake Logging event names without context. When you're debugging at midnight, payment_failed with no user ID, amount, or error code is nearly useless.

Log Context, Not Just Events

Python — Bad
# These tell you something happened — but not what, who, or why
logger.info('api_request')
logger.error('payment_failed')
logger.info('user_login')
Python — Good
# Rich context you can actually debug from
logger.info('api_request POST /api/v1/checkout 200 143ms user=u_8821')

logger.error(
    'payment_failed: Stripe declined card',
    extra={'extra_data': {
        'user_id': 'u_8821',
        'amount': 4999,
        'currency': 'USD',
        'stripe_error': 'insufficient_funds',
        'card_last4': '4242',
    }})

logger.info(
    'user_login success',
    extra={'extra_data': {
        'user_id': 'u_8821',
        'ip': '41.66.12.5',
        'method': 'email_password',
    }}
)

Use Log Levels Correctly

LevelWhen to useExample
DEBUGInternal state during developmentcache_miss key=user:8821:profile
INFONormal operations you want a record oforder_created order_id=ORD-991 total=$49.99
WARNINGUnexpected but the app recoveredrate_limit_approaching user=u_8821 75/100
ERRORSomething failed and needs attentiondb_query_failed table=orders timeout=30s
CRITICALService broken or data at riskdatabase_unreachable all_connections_exhausted

Always Use exc_info=True on Errors

Python
# Bad — no stack trace, impossible to debug
except Exception as e:
    logger.error(f'Payment failed: {e}')

# Good — full stack trace sent to Observo automatically
except Exception as e:
    logger.error('payment_processing_failed', exc_info=True, extra={'extra_data': {
        'order_id': order.id,
        'user_id': order.user_id,
        'amount': float(order.total),
    }})

Never Log Sensitive Data

Never logInstead log
Passwordslogin_attempt user=u_8821
Full credit card numberscard_last4=4242
API secrets or tokensapi_key_used=obs_...k9x2 (masked)
Social security numbersssn_verified=true
Private health infohealth_check_completed user=u_8821
Quick checklist before shipping a feature Does each log answer: Who triggered it (user_id)? What happened (action + outcome)? How long did it take (duration_ms)? Did it succeed or fail (status_code, error)? Is there a stack trace (exc_info=True on all errors)?

Alerting

Alert rules fire notifications when logs match conditions you define. Configure alerts from Project → Alert Rules → New Rule.

Rule Conditions

ConditionExample
levellevel = ERROR
message containsmessage contains "OutOfMemory"
count thresholdERROR count > 5 in 5 minutes
loggerlogger starts with "payments."

Notification Channels

Observo supports the following notification channels:

Email

Sends an HTML email with log context. Requires SMTP configuration in settings.

Slack

Posts a formatted message to a Slack channel via Incoming Webhook URL.

Webhook

POSTs a JSON payload to any URL. Works with PagerDuty, Discord, Teams, and more.

AI Analysis

Observo's AI features require an OPENAI_API_KEY in your environment. Once configured, you can ask questions about your logs in plain English.

.env
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o  # optional, defaults to gpt-4

With AI enabled, the Chat section of your dashboard lets you query logs conversationally — e.g. "Show me all errors in the last hour related to payments" or "What's causing the spike in warnings at 2am?"

Self-Hosting

Deploy Observo on your own server using Docker Compose.

Prerequisites

Docker, Docker Compose, and a server with at least 1 GB RAM.

Docker Compose

bash
# Clone the repo
git clone https://github.com/kennedy-ak/observo.git
cd observo

# Copy and edit environment file
cp .env.example .env
nano .env

# Start all services
docker-compose up -d

# Run migrations
docker-compose exec web python manage.py migrate

# Create your admin user
docker-compose exec web python manage.py createsuperuser
Services started by Docker Compose web (Django/Gunicorn), worker (Celery), beat (Celery scheduler), db (PostgreSQL), redis (Redis)

Configuration

Configure Observo via environment variables in your .env file.

VariableRequiredDescription
SECRET_KEYYesDjango secret key — use a long random string
DATABASE_URLYespostgres://user:pass@host:5432/db
REDIS_URLYesredis://localhost:6379/0
DEBUGNoSet to False in production
ALLOWED_HOSTSNoComma-separated list of allowed hostnames
OPENAI_API_KEYNoRequired for AI features
OPENAI_MODELNoOpenAI model name (default: gpt-4)
EMAIL_HOSTNoSMTP host for email alerts
EMAIL_PORTNoSMTP port (default: 587)
EMAIL_HOST_USERNoSMTP username
EMAIL_HOST_PASSWORDNoSMTP password