Skip to content

Logging & Observability

SentriKat writes structured logs to /var/log/sentrikat/ inside the container. Logs are split per concern so SOC2/SIEM consumers can ingest the security and audit streams independently from application noise.

Log files

File Level Content Retention
application.log INFO+ App boot, scheduler ticks, Flask request handlers info/warning 10 MB × 10 backups (~100 MB)
error.log ERROR Uncaught exceptions, sanitized 500 errors, stack traces 10 MB × 10 backups
access.log INFO HTTP request access (method, path, status, IP, UA) 20 MB × 10 backups
security.log WARNING+ Auth events, license rejections, permission denials, lockouts 10 MB × 20 backups (~200 MB)
audit.log INFO (JSON) Privileged CRUD on user/role/setting/integration 20 MB × 50 backups (~1 GB)
ldap.log INFO LDAP operations (bind, search, sync, auto-provision) 10 MB × 10 backups
performance.log INFO (JSON) Slow queries (>200ms), slow endpoints 20 MB × 10 backups

Rotation is automatic (Python logging.handlers.RotatingFileHandler). Retention can be tuned by editing app/logging_config.py:setup_logging.

Ownership and permissions

All log files are owned by the sentrikat system user (UID assigned at build time — check with docker compose exec sentrikat id) inside the container. The entrypoint docker-entrypoint.sh drops privileges from root to sentrikat via gosu before starting gunicorn, so the master and all workers create files with the correct ownership.

If you ever see logs only populated with the boot lines (~6 entries in application.log, everything else 0 bytes), check ownership first:

docker exec sentrikat ls -la /var/log/sentrikat/
# Expected: every file 'sentrikat sentrikat'
# Wrong:    'root root' → privilege drop didn't happen

Mounting logs to host

By default /var/log/sentrikat/ is a tmpfs (in-memory) under the read-only rootfs. Logs are lost on container restart. To persist:

Option A — STORAGE_ROOT env var

services:
  sentrikat:
    environment:
      STORAGE_ROOT: /data/sentrikat
    volumes:
      - /host/path/sentrikat-data:/data/sentrikat

The entrypoint derives LOG_DIR=$STORAGE_ROOT/logs, DATA_DIR=$STORAGE_ROOT/data, BACKUP_DIR=$STORAGE_ROOT/backups automatically. Make sure the host path is owned by the container's sentrikat UID (check with docker compose exec sentrikat id):

sudo chown -R 999:999 /host/path/sentrikat-data

Option B — explicit LOG_DIR

environment:
  LOG_DIR: /var/log/sentrikat
volumes:
  - /host/path/sentrikat-logs:/var/log/sentrikat

If the host path can't be chowned (NFS readonly), the entrypoint falls back to /app/logs inside the container (lost on restart).

Shipping logs externally

Syslog forwarding

environment:
  RSYSLOG_REMOTE: "syslog.example.com:514"
  RSYSLOG_PROTOCOL: udp  # or tcp

Elasticsearch / Kibana

environment:
  ELASTIC_HOST: https://es.example.com:9200
  ELASTIC_INDEX_PREFIX: sentrikat-

Diagnostics — logs not populating

# Check ownership
docker exec sentrikat ls -la /var/log/sentrikat/

# Check line counts
docker exec sentrikat sh -c "wc -l /var/log/sentrikat/*.log"

# Confirm gunicorn workers re-initialized post_fork
docker logs sentrikat 2>&1 | grep "re-initialized DB pool + logging"
# Expected: one line per worker (typically 4-16 lines)

See also