Skip to content
DivkixPublic

About

Self-hosted logging platform with real-time streaming, full-text search, and OTLP-compatible ingestion. Deploy in minutes, own your data.

Topics

Resources

Stars

44 stars

Watchers

0 watching

Forks

Latest commit

 

History

480 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logwell - Self-hosted logging for high-performance apps

Logwell is a self-hosted logging platform with real-time streaming, full-text search, and OTLP-compatible ingestion. Deploy in minutes, own your data.

Alpha Software — Expect breaking changes. Not recommended for production workloads.

License: MIT GitHub stars Build Status npm version JSR PyPI version Go Reference TypeScript Python Go Node.js pnpm

Features • Quick Start • Usage • Deploy • Contributing • License


What is Logwell?

Logwell is a lightweight, self-hosted log aggregation platform for developers who want structured logging without the complexity of ELK or the costs of cloud services.

Use Logwell when you need:

  • A simple logging backend for your side project or startup
  • Full-text search across logs without managing Elasticsearch
  • Real-time log streaming during development and debugging
  • Complete data ownership with no vendor lock-in

Logwell is NOT for:

  • High-volume production systems (10k+ logs/second) — use Loki or Clickhouse
  • Teams needing RBAC, audit trails, or compliance features — use a managed service
  • Distributed tracing or metrics — Logwell is logs-only

Features

  • OTLP-native ingestion — Standard OpenTelemetry protocol, no proprietary SDKs required
  • PostgreSQL backend — Full-text search via tsvector, no separate search cluster needed
  • Real-time streaming — SSE-powered live log tailing with batching
  • Incident intelligence — Error/fatal fingerprinting with incident timeline and correlation hints
  • Project isolation — Per-project API keys with separate log streams
  • Zero telemetry — No phone-home, no tracking, fully air-gapped deployments supported
  • Clean UI — Minimal interface with dark mode and log level color coding

Preview


Real-time log streaming demo

Log Viewer

Real-time log viewer with level filtering

Quick Start

Quick Start with pre-filled API key

Project Settings

API key management and code snippets

Statistics

Log level distribution analytics

Why Logwell?

vs Logwell advantage
Loki/Grafana Built-in UI, no LogQL to learn, just PostgreSQL
ELK Lightweight PostgreSQL backend, not Elasticsearch
Datadog/etc Self-hosted, no per-GB pricing, own your data

Tech Stack

Layer Technology
Framework SvelteKit
Database PostgreSQL
ORM Drizzle
Auth better-auth
UI shadcn-svelte + Tailwind CSS v4
Real-time Server-Sent Events
Runtime Node.js

Prerequisites

  • Node.js ^22.22.2, ^24.15.0, or >=26 (runtime, build, tests, and Vite+ hooks). CI and the Docker image use Node 24 LTS.
  • pnpm 12 (packageManager pins the exact version)
  • Docker (for PostgreSQL)

Quick Start

# Clone the repository
git clone https://github.com/divkix/logwell.git
cd logwell

# Install dependencies
pnpm install

# Set up environment
cp .env.example .env
# Edit .env with your values (see Environment Variables below)

# Start PostgreSQL
docker compose up -d

# Run database migrations
pnpm run db:migrate

# Create admin user
pnpm run db:seed

# Start development server
pnpm run dev

Open http://localhost:5173 and sign in with:

  • Username: admin (or your ADMIN_USERNAME from .env)
  • Password: Your ADMIN_PASSWORD from .env

Note: Development runs on port 5173 (Vite). Production builds run on port 3000.

Dependency management

The app and TypeScript SDK are one pnpm workspace with a shared root pnpm-lock.yaml. From the repository root:

pnpm install                     # both JS projects
pnpm up -r                       # update both within declared ranges
pnpm --filter logwell-app up     # app only
pnpm --filter logwell up         # TypeScript SDK only

To update pnpm itself, run pnpm self-update here and update the pinned version and release-asset checksums in Dockerfile.

Vite+/Vite/Vitest share versions in the root workspace catalog. Upgrade them together from the workspace root with the current global vp migrate --full --no-interactive (not one package at a time). Python (sdks/python/uv.lock) and Go (sdks/go/go.mod) remain separate ecosystems; update them with uv lock --upgrade and go get -u ./... from their respective SDK directories.

Environment Variables

Create a .env file with the following:

# Database connection
DATABASE_URL="postgres://root:mysecretpassword@localhost:5432/local"

# Authentication secret (minimum 32 characters): openssl rand -base64 32
# Published placeholder values (e.g. the old example secret) are rejected at startup.
BETTER_AUTH_SECRET=""

# Admin user password (minimum 8 characters)
ADMIN_PASSWORD="your-admin-password"

# Admin username (optional, defaults to "admin")
# ADMIN_USERNAME="admin"

# Public URL the browser uses (required: CSRF checks and sign-in/out compare against it).
# Production without it logs a startup warning; an https ORIGIN also turns on HSTS.
ORIGIN="https://your-domain.com"

# NODE_ENV: only "development" and "test" relax validation. Unset, "production",
# "staging" and any other value get production rules (strict secret, Secure cookies).

# Log retention (optional, defaults shown)
# LOG_RETENTION_DAYS="30"        # 0 = never auto-delete
# LOG_CLEANUP_INTERVAL_MS="3600000"  # Cleanup job interval (1 hour); also purges deleted projects
# LOG_CLEANUP_BATCH_PAUSE_MS="100"   # Pause between retention/purge delete batches (0-60000)
# INCIDENT_AUTO_RESOLVE_MINUTES="30" # Incident resolves after N quiet minutes

# Rate limiting (optional, defaults shown)
# RATE_LIMIT_INGEST_RPM="600"    # Max ingest requests per minute per API key
# RATE_LIMIT_INGEST_IP_RPM="60000" # Max pre-auth /v1 requests per minute per client IP
# RATE_LIMIT_LOGIN_RPM="10"      # Max login attempts per minute per IP (IPv6 grouped by /64)
# RATE_LIMIT_LOGIN_ACCOUNT_RPM="30" # Max FAILED logins per minute per account, from any address

# Database timeouts (optional, defaults shown; whole ms, 0 = server default, max 3600000)
# DB_STATEMENT_TIMEOUT_MS="30000"            # Longer queries are cancelled -> 503 query_timeout
# DB_IDLE_IN_TRANSACTION_TIMEOUT_MS="60000"

# Live streams and ingest (optional, defaults shown)
# SSE_MAX_CONNECTIONS="200"          # Open live streams per process; over the cap -> 429
# INGEST_MAX_FUTURE_SKEW_MS="300000" # Timestamps further ahead are clamped to receive time

An invalid database timeout fails startup; other invalid numbers fall back to the default with a warning. .env.example documents every variable. The two database timeouts are sent as connection startup parameters: behind PgBouncer in transaction mode, add ignore_startup_parameters = statement_timeout,idle_in_transaction_session_timeout (and set the timeouts on the role or database instead), or connections fail.

Generate a secure secret:

openssl rand -base64 32

Usage

Create a Project

  1. Sign in to the dashboard
  2. Click New Project
  3. Enter a project name
  4. Copy the generated API key (lw_...)

Send Logs

Logwell provides two ingestion APIs:

API Endpoint Best For
Simple API POST /v1/ingest Quick integration, any HTTP client
OTLP API POST /v1/logs OpenTelemetry SDKs, rich metadata

Simple API (Recommended for quick start)

The simple API accepts flat JSON with minimal boilerplate:

curl -X POST http://localhost:5173/v1/ingest \
  -H "Authorization: Bearer lw_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"level": "info", "message": "User signed in"}'

Batch multiple logs:

curl -X POST http://localhost:5173/v1/ingest \
  -H "Authorization: Bearer lw_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {"level": "info", "message": "Request started"},
    {"level": "error", "message": "Database timeout", "metadata": {"query": "SELECT..."}}
  ]'

Available fields:

Field Required Type Description
level Yes debug | info | warn | error | fatal Log severity
message Yes string Log message
timestamp No ISO8601 string Defaults to current time
service No string Service name for filtering
metadata No object Additional structured data

Limits (both APIs): a batch holds at most 100 logs. JSON nested deeper than 256 levels rejects the whole request (400 validation_error). A message (or OTLP string body) longer than 98,304 characters is stored truncated with a … [truncated, N characters] marker. A record whose structured fields (metadata, attributes, resource, scope, non-string body) serialize to more than 65,536 characters is rejected on its own: the response is still 200 with accepted, rejected and errors[]. Timestamps more than INGEST_MAX_FUTURE_SKEW_MS (5 minutes) ahead of the server clock, or before year 1, are replaced with the receive time.

Node.js (no SDK needed)
await fetch("http://localhost:5173/v1/ingest", {
  method: "POST",
  headers: {
    Authorization: "Bearer lw_YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ level: "info", message: "Hello from Node.js" }),
});
Python (no SDK needed)
import requests

requests.post('http://localhost:5173/v1/ingest',
    headers={'Authorization': 'Bearer lw_YOUR_API_KEY'},
    json={'level': 'info', 'message': 'Hello from Python'})
Go (no SDK needed)
body := []byte(`{"level": "info", "message": "Hello from Go"}`)
req, _ := http.NewRequest("POST", "http://localhost:5173/v1/ingest", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer lw_YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)

TypeScript/JavaScript SDK

For Node.js, browsers, and edge runtimes (Cloudflare Workers, etc.):

npm install logwell
import { Logwell } from "logwell";

const logger = new Logwell({
  apiKey: "lw_YOUR_API_KEY",
  endpoint: "http://localhost:5173",
});

// Log at different levels
logger.info("User signed in", { userId: "123" });
logger.error("Database failed", { host: "db.local" });

// Flush before shutdown
await logger.shutdown();

Features: Zero dependencies, automatic batching, retry with backoff, TypeScript-first, opt-in source location capture.

Deno users: Install from JSR with deno add jsr:@divkix/logwell and import from @divkix/logwell

Full SDK documentation →


Python SDK

For Python applications with automatic batching and retries:

pip install logwell
import asyncio
from logwell import Logwell

client = Logwell({
    'api_key': 'lw_YOUR_API_KEY',
    'endpoint': 'http://localhost:5173',
    'service': 'my-app',
})

# Log at different levels
client.info('User signed in', {'user_id': '123'})
client.error('Database failed', {'host': 'db.local'})

# Flush before shutdown
asyncio.run(client.shutdown())

Features: Async batching, automatic retries with backoff, child loggers, typed errors, source location capture.

Full SDK documentation →


Go SDK

For Go applications with zero external dependencies:

go get github.com/Divkix/Logwell/sdks/go
package main

import (
    "context"
    "log"

    "github.com/Divkix/Logwell/sdks/go/logwell"
)

func main() {
    client, err := logwell.New(
        "http://localhost:5173",
        "lw_YOUR_API_KEY",
        logwell.WithService("my-app"),
    )
    if err != nil {
        log.Fatal(err)
    }

    // Log at different levels
    client.Info("User signed in", logwell.M{"userId": "123"})
    client.Error("Database failed", logwell.M{"host": "db.local"})

    // Flush before shutdown
    if err := client.Shutdown(context.Background()); err != nil {
        log.Printf("Shutdown error: %v", err)
    }
}

Features: Zero dependencies, automatic batching, retry with backoff, child loggers, source location capture.

Full SDK documentation →


OTLP API (For OpenTelemetry Users)

For applications already using OpenTelemetry, point your OTLP log exporter to POST /v1/logs with your API key in the Authorization header.

Node.js / TypeScript
npm install @opentelemetry/exporter-logs-otlp-http @opentelemetry/sdk-logs
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { LoggerProvider, BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";

const exporter = new OTLPLogExporter({
  url: "http://localhost:5173/v1/logs",
  headers: { Authorization: "Bearer lw_YOUR_API_KEY" },
});

const loggerProvider = new LoggerProvider();
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(exporter));
Python
pip install opentelemetry-exporter-otlp-proto-http
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor

exporter = OTLPLogExporter(
    endpoint="http://localhost:5173/v1/logs",
    headers={"Authorization": "Bearer lw_YOUR_API_KEY"},
)

logger_provider = LoggerProvider()
logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter))
Go
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
import (
    "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
    "go.opentelemetry.io/otel/sdk/log"
)

exporter, _ := otlploghttp.New(ctx,
    otlploghttp.WithEndpointURL("http://localhost:5173/v1/logs"),
    otlploghttp.WithHeaders(map[string]string{
        "Authorization": "Bearer lw_YOUR_API_KEY",
    }),
)

processor := log.NewBatchProcessor(exporter)
provider := log.NewLoggerProvider(log.WithProcessor(processor))
Java
<!-- Maven -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;

OtlpHttpLogRecordExporter exporter = OtlpHttpLogRecordExporter.builder()
    .setEndpoint("http://localhost:5173/v1/logs")
    .addHeader("Authorization", "Bearer lw_YOUR_API_KEY")
    .build();
C# / .NET
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
using OpenTelemetry;
using OpenTelemetry.Exporter;

builder.Logging.AddOpenTelemetry(logging =>
    logging.AddOtlpExporter(options =>
    {
        options.Endpoint = new Uri("http://localhost:5173/v1/logs");
        options.Protocol = OtlpExportProtocol.HttpProtobuf;
        options.Headers = "Authorization=Bearer lw_YOUR_API_KEY";
    }));

OTLP Attribute Mapping

Logwell derives some UI fields from common OTLP log attributes (if present):

UI field Preferred OTLP attribute keys
sourceFile code.filepath, source.file
lineNumber code.lineno, source.line
requestId request.id, http.request_id
userId enduser.id, user.id
ipAddress client.address, net.peer.ip, net.sock.peer.addr

Commands

Command Description
pnpm run dev Start development server
pnpm run build Build for production
pnpm run preview Preview production build
pnpm run check Run TypeScript checks
pnpm run lint Run linter
pnpm run lint:fix Fix lint issues

Database

Command Description
pnpm run db:start Start PostgreSQL via Docker
pnpm run db:push Push schema to database (dev only; prefer db:migrate)
pnpm run db:generate Generate migration files
pnpm run db:migrate Run migrations (under an advisory lock; loads .env if present)
pnpm run db:studio Open Drizzle Studio
pnpm run db:seed Create admin user

Testing

Command Description
pnpm run test Run all tests
pnpm run test:unit Run unit tests
pnpm run test:integration Run integration tests
pnpm run test:component Run component tests
pnpm run test:e2e Run E2E tests (Playwright)
pnpm run test:coverage Run tests with coverage
pnpm run test:ui Open Vitest UI

CI builds before Playwright tests and launches Vite+ preview directly; wrapping preview in pnpm run leaves its process running after the tests.

Production Deployment

One-Click Deploy

Deploy on Railway Deploy to Render

Fly.io: Clone the repo and run fly launch (uses included fly.toml)

Note: PostgreSQL database required. Railway has $5/mo free credit. The Render Blueprint asks for ADMIN_PASSWORD and uses a paid Postgres plan, because free Render Postgres expires after 30 days. On Fly.io run exactly one always-on machine (fly.toml sets min_machines_running = 1 and turns auto-stop off): rate limits and live streams are per process.

Docker Compose (Recommended)

The easiest way to deploy Logwell with PostgreSQL:

# Set required environment variables
export BETTER_AUTH_SECRET=$(openssl rand -base64 32)
export ADMIN_PASSWORD="your-secure-admin-password"
export DB_PASSWORD="$(openssl rand -base64 24)"
export ORIGIN="https://logs.example.com"

# Optional: run a release other than the one compose.prod.yaml is pinned to
# export LOGWELL_VERSION="v1.5.0"

# Start the full stack
docker compose -f compose.prod.yaml up -d

# View logs
docker compose -f compose.prod.yaml logs -f app

# Stop the stack
docker compose -f compose.prod.yaml down

compose.prod.yaml pins the image to the release it shipped with, so a restart never pulls a new version (and its migrations) on its own. For backups, restores, upgrades and rollbacks, see docs/operations.md.

Upgrading a large install to the log index rework (migration 0013)

Migration 0013 replaces the log indexes with a plain CREATE INDEX, which blocks writes to the log table while it builds (roughly 1-2 s per index per 2M rows). On a big table, build the new indexes online before deploying:

CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_log_project_timestamp_id ON log (project_id, timestamp, id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_log_project_level_timestamp_id ON log (project_id, level, timestamp, id);
-- Any row here is an INVALID index: DROP INDEX CONCURRENTLY it and build it again.
SELECT indexrelid::regclass FROM pg_index WHERE indrelid = 'log'::regclass AND NOT indisvalid;

The migration aborts with a hint if either index exists but is invalid. Boot migrations run under a Postgres advisory lock, so replicas starting together wait for one another.

Docker (App Only)

If you have an external PostgreSQL database:

Using Pre-built Image

# Pick a release tag (see GitHub Releases) and pull it from GitHub Container Registry
export LOGWELL_VERSION="v1.5.0"
docker pull "ghcr.io/divkix/logwell:$LOGWELL_VERSION"

# Generate the secret once and keep it: a new one signs everyone out
export BETTER_AUTH_SECRET="$(openssl rand -base64 32)"

# Run the container
docker run -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/db" \
  -e BETTER_AUTH_SECRET \
  -e ADMIN_PASSWORD="your-admin-password" \
  -e ORIGIN="http://localhost:3000" \
  -e NODE_ENV=production \
  "ghcr.io/divkix/logwell:$LOGWELL_VERSION"

Building Locally

# Build the image
docker build -t logwell .

# Generate the secret once and keep it: a new one signs everyone out
export BETTER_AUTH_SECRET="$(openssl rand -base64 32)"

# Run the container
docker run -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/db" \
  -e BETTER_AUTH_SECRET \
  -e ADMIN_PASSWORD="your-admin-password" \
  -e ORIGIN="http://localhost:3000" \
  -e NODE_ENV=production \
  logwell

Health Check

The app exposes a health check endpoint for monitoring:

curl http://localhost:3000/api/health

Response:

{
  "status": "healthy",
  "database": "connected",
  "timestamp": "2025-01-02T12:00:00.000Z",
  "uptime": 3600
}
  • Returns 200 OK when healthy
  • Returns 503 Service Unavailable (with "status": "unhealthy") when the database is down or the probe takes longer than 3 seconds
  • Reports no version or build detail: the endpoint is public

Manual

pnpm run build
node ./build/index.js

The app runs on port 3000 by default.

API Reference

Health (Public)

Endpoint Method Description
/api/health GET Health check with database status

Log Ingestion (API Key Auth)

Endpoint Method Description
/v1/ingest POST Simple JSON log ingestion
/v1/logs POST OTLP/HTTP JSON log export

Project Management (Session Auth)

Endpoint Method Description
/api/projects GET List projects (logCount capped at 10,000, lastActivity)
/api/projects POST Create project
/api/projects/[id] GET Get project details
/api/projects/[id] PATCH Update project (name, retention)
/api/projects/[id] DELETE Delete project (hidden and key revoked now, data purged later)
/api/projects/[id]/regenerate POST Regenerate API key
/api/projects/[id]/logs GET Query logs
/api/projects/[id]/logs/stream POST SSE stream
/api/projects/[id]/incidents GET List incidents (open/resolved, range filters)
/api/projects/[id]/incidents/[incidentId] GET Incident detail with root-cause candidates
/api/projects/[id]/incidents/[incidentId]/timeline GET Incident timeline buckets + peak
/api/projects/[id]/incidents/stream POST SSE incident updates
/api/projects/[id]/stats GET Level distribution (from/to: default 24h, max 7 days)
/api/projects/[id]/logs/export GET Export logs as CSV or JSON (?format=csv|json, max 10,000)

Query limits and errors

Error responses are JSON {error, message}; the codes below are the error value.

  • Capped counts: GET /api/projects and GET /api/projects/[id] count at most 10,000 logs per project (logCountCapped / totalLogsCapped say when the cap was hit; read a capped value as "10,000+"). Once capped, the detail's levelCounts describe the newest 10,000 logs.
  • Logs: prefer cursor pagination. The deprecated offset is capped at 10,000, and a higher value returns 400 offset_too_large. A malformed or out-of-range cursor returns 400 invalid_cursor. A search longer than 256 characters returns 400 invalid_search.
  • Stats: /stats defaults to the last 24 hours and accepts a window of at most 7 days. A window over 7 days or from after to returns 400 invalid_range. A malformed date returns 400 invalid_date. The response echoes the effective from and to.
  • Incident detail: root-cause candidates and correlations cover the incident's newest 10,000 events (analyzedEvents).
  • Timeouts: a query cancelled by DB_STATEMENT_TIMEOUT_MS returns 503 {error: "query_timeout", message, id}. Narrow the query and retry.
  • Live streams: past SSE_MAX_CONNECTIONS open streams, a stream request returns 429 too_many_streams with Retry-After: 60. The dashboard waits and reconnects.

Current Limitations

Limitation Impact Workaround
Single-user auth No team collaboration Share credentials (not recommended)
No alerting / notifications Incidents are detected but not pushed (no webhook/email) Poll the incidents API or SSE stream
No programmatic read API Logs are readable via the UI or session-auth endpoints only, not via API key Use session-auth /api/projects/[id]/logs endpoint

Security

  • Always use TLS — Run behind a reverse proxy (nginx, Caddy) with HTTPS in production
  • Protect API keys — Treat lw_* keys as secrets; they grant write access to your logs
  • Network isolation — Consider firewall rules to restrict /v1/logs access to known sources
  • Behind a proxy — Set ADDRESS_HEADER and XFF_DEPTH, or per-IP rate limits see the proxy's address
  • Built-in hardening — Every response sends X-Content-Type-Options: nosniff, X-Frame-Options: DENY and Referrer-Policy: same-origin (plus HSTS when ORIGIN is https). Pages carry a nonce-based Content Security Policy. Logins are limited per client network and per account (failed attempts only; a network the account signed in from before is not locked out by failures from elsewhere).

Troubleshooting

Issue Solution
Database connection refused Ensure PostgreSQL is running: docker compose up -d
Admin seed fails Check ADMIN_PASSWORD is at least 8 characters
Auth errors / startup fails BETTER_AUTH_SECRET must be 32+ characters and not a placeholder
Port 5432 in use Stop other PostgreSQL instances or change port in compose.yaml

Badge

Using Logwell? Add the badge to your project:

[![Powered by Logwell](https://img.shields.io/badge/Powered%20by-Logwell-111111?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3Qgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHJ4PSIxMTUiIGZpbGw9IndoaXRlIi8+PHJlY3QgeD0iMTI4IiB5PSIxNDgiIHdpZHRoPSIyNTYiIGhlaWdodD0iNDgiIHJ4PSIyNCIgZmlsbD0iIzExMTExMSIvPjxyZWN0IHg9IjEyOCIgeT0iMjI4IiB3aWR0aD0iMjU2IiBoZWlnaHQ9IjQ4IiByeD0iMjQiIGZpbGw9IiMxMTExMTEiIGZpbGwtb3BhY2l0eT0iMC42Ii8+PHJlY3QgeD0iMTI4IiB5PSIzMDgiIHdpZHRoPSIxNjAiIGhlaWdodD0iNDgiIHJ4PSIyNCIgZmlsbD0iIzExMTExMSIgZmlsbC1vcGFjaXR5PSIwLjI1Ii8+PC9zdmc+)](https://github.com/divkix/logwell)

Powered by Logwell

Contributing

Contributions are welcome! Here's how to get started:

# Fork and clone the repo
git clone https://github.com/YOUR_USERNAME/logwell.git
cd logwell

# Install dependencies
pnpm install

# Start dev environment
docker compose up -d
pnpm run db:migrate
pnpm run dev

Before submitting a PR:

  • Run pnpm run check (TypeScript)
  • Run vp check (lint + format + typecheck)
  • Run pnpm run test (Vitest)
  • Use conventional commits (feat:, fix:, docs:, etc.)

Report bugs: GitHub Issues

License

MIT

About

Self-hosted logging platform with real-time streaming, full-text search, and OTLP-compatible ingestion. Deploy in minutes, own your data.

Topics

Resources

Stars

44 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages