Open-source · Multi-tenant · Real-time

Structured logs,
without the complexity

PIJ Logger collects, stores, and streams structured logs from all your projects. Ship faster by spending less time grepping files.

Start for free Sign in
Real-time streaming

Watch logs flow in live via WebSocket. Pause, resume, and filter without missing a line.

Powerful history search

Filter by level, source, and date range. Cursor-paginated so even massive log volumes load fast.

Multi-tenant projects

Separate log streams per project, each with its own API tokens and configurable retention.

Token-scoped access

Create per-environment tokens (live / staging / dev). Revoke instantly without touching your app.

Zero-dependency SDKs

Python and JavaScript SDKs ship with no external dependencies, batching, and automatic retry.

Dark mode

The log terminal is always dark. The rest of the UI follows your OS preference or manual toggle.

Real-time Telegram alerts

Get pinged the moment your app logs an error, with smart flood control. One alert fires immediately; the rest collapse into a digest.

AI anomaly detection

Automatically spots error-rate spikes, volume surges and brand-new failures, with a short AI brief of what happened, the likely cause, and the affected source.

Send logs in seconds

Pick any integration โ€” they all land in the same real-time stream.

# Send a single log entry
curl -X POST https://log.pijcore.com/api/logs/ingest/ \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "level": "INFO",
    "message": "User signed up",
    "source": "auth-service",
    "meta": {"user_id": 42, "plan": "free"}
  }'

# Or batch-send multiple entries at once
curl -X POST https://log.pijcore.com/api/logs/ingest/batch/ \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '[
    {"level": "DEBUG", "message": "Cache miss", "source": "cache"},
    {"level": "ERROR", "message": "Payment failed", "source": "billing"}
  ]'
from pij_logger import Logger

logger = Logger(
    token="<your-token>",
    base_url="https://log.pijcore.com",
    source="my-service",
    batch_size=50,       # flush after 50 queued entries
    flush_interval=5,  # or every 5 seconds
)

# Convenience level methods
logger.debug("Cache hit", meta={"key": "user:42"})
logger.info("User signed up", meta={"user_id": 42})
logger.warning("Rate limit approaching")
logger.error("Payment failed", meta={"code": "CARD_DECLINED"})
logger.critical("Database unreachable")

# Use as context manager โ€” flushes on exit
with Logger(token="<token>") as log:
    log.info("Job started")
    # ... do work ...
    log.info("Job finished")
import { Logger } from 'pij-logger';

const logger = new Logger({
  token: '<your-token>',
  baseUrl: 'https://log.pijcore.com',
  source: 'my-service',
  batchSize: 50,
  flushInterval: 5,
});

// Async convenience methods
await logger.debug('Cache hit', { meta: { key: 'user:42' } });
await logger.info('User signed up', { meta: { userId: 42 } });
await logger.warning('Rate limit approaching');
await logger.error('Payment failed', { meta: { code: 'CARD_DECLINED' } });

// Flush before process exit
await logger.close();
# Drop-in logger โ€” stdlib + certifi. Copy into your project. No SDK needed.
import json, ssl, urllib.request

_UA = "my-app/1.0"  # NOT the default: Cloudflare 403s "Python-urllib"
try:
    import certifi
    _CTX = ssl.create_default_context(cafile=certifi.where())
except Exception:                           # no certifi -> system trust store
    _CTX = ssl.create_default_context()


class PijLogger:
    def __init__(self, token, source=None, base_url="https://log.pijcore.com"):
        self._url = base_url.rstrip("/") + "/api/logs/ingest/"
        self._headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
            "User-Agent": _UA,                    # fix: avoid Cloudflare 403
        }
        self._source = source

    def log(self, level, message, meta=None):
        body = {"level": level, "message": message}
        if self._source: body["source"] = self._source
        if meta: body["meta"] = meta
        req = urllib.request.Request(
            self._url, method="POST", headers=self._headers,
            data=json.dumps(body).encode(),
        )
        try:
            urllib.request.urlopen(req, timeout=5, context=_CTX)  # fix: TLS
        except Exception as e:
            print(f"[PijLogger] delivery failed: {e}")  # don't swallow silently

    def debug(self, m, meta=None):    self.log("DEBUG", m, meta)
    def info(self, m, meta=None):     self.log("INFO", m, meta)
    def warning(self, m, meta=None):  self.log("WARNING", m, meta)
    def error(self, m, meta=None):    self.log("ERROR", m, meta)
    def critical(self, m, meta=None): self.log("CRITICAL", m, meta)


# Usage
log = PijLogger(token="<your-token>", source="my-service")
log.info("User signed up", meta={"user_id": 42})
log.error("Payment failed", meta={"code": "CARD_DECLINED"})
// Drop-in logger โ€” plain fetch (Node 18+ / browser), no SDK. Copy into your project.
class PijLogger {
  constructor({ token, source, baseUrl = "https://log.pijcore.com" }) {
    this._url = baseUrl.replace(/\/$/, "") + "/api/logs/ingest/";
    this._headers = {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json",
      "User-Agent": "my-app/1.0",  // avoids WAF 403 in Node; browsers ignore it
    };
    this._source = source;
  }

  log(level, message, meta) {
    const body = { level, message };
    if (this._source) body.source = this._source;
    if (meta) body.meta = meta;
    return fetch(this._url, {
      method: "POST",
      headers: this._headers,
      body: JSON.stringify(body),
    }).catch((e) => console.error("[PijLogger] delivery failed:", e));  // don't swallow
  }

  debug(m, meta)    { return this.log("DEBUG", m, meta); }
  info(m, meta)     { return this.log("INFO", m, meta); }
  warning(m, meta)  { return this.log("WARNING", m, meta); }
  error(m, meta)    { return this.log("ERROR", m, meta); }
  critical(m, meta) { return this.log("CRITICAL", m, meta); }
}

// Usage โ€” await in short scripts, or the process may exit before it sends.
const log = new PijLogger({ token: "<your-token>", source: "my-service" });
await log.info("User signed up", { user_id: 42 });
await log.error("Payment failed", { code: "CARD_DECLINED" });

Before you connect โ€” good to know

A few things that otherwise make ingestion silently fail โ€” the snippets above already handle them.