Documentation
Everything you need to go from zero to searchable logs. Ten minutes, tops.
Getting started
From zero to searchable logs in four steps:
- Set up your ZipLogger workspace (below) and sign in.
- Create an ingestion API key under Settings → API keys.
- Add a ZipLogger package to your app — or point your OTel exporter at it.
- Open Search and watch your logs arrive live.
Setting up your workspace
ZipLogger is self-hosted: it runs on your own server, so your logs never leave your infrastructure. Setup is a guided, single-command installation that takes about ten minutes on any modest cloud VM or on-prem machine — everything ZipLogger needs is included, and the step-by-step deployment guide ships with the product.
Once it's up, sign in with the administrator account created during setup, invite your team under Team, and pick a plan under Billing (the Free plan is active by default).
.NET integration
Two NuGet packages cover the standard .NET logging abstractions. Both share the same transport: a bounded in-memory queue, NDJSON batching, retry with exponential backoff (429-aware), and drop-on-backpressure — a logging call never blocks or throws in your application.
Both packages also enrich every event automatically with the release (assembly informational
version), commit SHA (SourceLink suffix or GIT_COMMIT), environment, and
machine name — the commit SHA is what powers git regression detection.
Serilog
dotnet add package ZipLogger.Serilog
Log.Logger = new LoggerConfiguration()
.WriteTo.ZipLogger("https://logs.yourcompany.com", "zk_...")
.CreateLogger();
Log.Information("Order {OrderId} created for {Customer}", 83112, "acme");
// → structured fields OrderId + Customer, searchable instantly
Destructured objects ({@Cart}), the message template, and exceptions are preserved as
structured fields; exceptions map to the stack-trace field that feeds regression detection.
Microsoft ILogger
dotnet add package ZipLogger.Extensions.Logging
// Program.cs
builder.Logging.AddZipLogger(options =>
{
options.Endpoint = "https://logs.yourcompany.com";
options.ApiKey = "zk_...";
});
Or configure from appsettings.json under Logging:ZipLogger — including
standard per-category level filtering. Scopes are captured as fields, and
logger.LogError(ex, ...) ships the full exception.
Python
pip install ziplogger
import logging
from ziplogger import ZipLoggerHandler
logging.getLogger().addHandler(ZipLoggerHandler(
endpoint="https://logs.yourcompany.com",
api_key="zk_...",
))
log = logging.getLogger("app.orders")
log.info("Order %s created", 83112, extra={"orderId": 83112})
log.exception("Payment failed") # traceback → stackTrace
Standard library only — no dependencies. extra= values become searchable fields,
the logger name becomes category, and tracebacks feed git regression detection.
Same delivery semantics as the .NET client: bounded queue, batching, 429-aware retries, and a
handler that never blocks or raises.
Node.js
npm install ziplogger
Pino
const pino = require('pino')
const logger = pino(pino.transport({
target: 'ziplogger/pino',
options: { endpoint: 'https://logs.yourcompany.com', apiKey: 'zk_...' },
}))
logger.info({ orderId: 83112 }, 'Order created')
Winston
const { ZipLoggerTransport } = require('ziplogger/winston')
const logger = winston.createLogger({
transports: [new ZipLoggerTransport({ endpoint: 'https://...', apiKey: 'zk_...' })],
})
A zero-dependency core client (ZipLoggerClient) is also exported for custom
setups. Error objects map to stackTrace; timers are unrefed so the
SDK never keeps your process alive.
Go
go get github.com/ahaliav/ziplogger/sdk_go
client, err := ziplogger.New(ziplogger.Options{
Endpoint: "https://logs.yourcompany.com",
APIKey: "zk_...",
})
defer client.Close(5 * time.Second)
logger := slog.New(ziplogger.NewSlogHandler(client, slog.LevelInfo))
logger.Info("order created", "orderId", 83112)
logger.Error("payment failed", "err", err) // → stackTrace
Standard library only. slog attributes become searchable fields (groups are
dot-prefixed); an err attribute maps to the exception fields that feed regression
detection. The direct client.Log(ziplogger.Entry{...}) API is there for
everything else.
Java
<dependency>
<groupId>dev.ziplogger</groupId>
<artifactId>ziplogger</artifactId>
<version>0.1.0</version>
</dependency>
var client = new ZipLoggerClient(new ZipLoggerClient.Options(
"https://logs.yourcompany.com", "zk_..."));
Logger.getLogger("").addHandler(new ZipLoggerJulHandler(client, true));
log.info("order created");
log.log(Level.SEVERE, "payment failed", exception); // → stackTrace
JDK-only (Java 17+), zero dependencies. A java.util.logging handler ships today;
log through the client directly from SLF4J/Logback setups — native appenders are on the
roadmap.
Browser / React
npm install @ziplogger/browser
import { ZipLoggerBrowser } from '@ziplogger/browser'
import { createErrorBoundary } from '@ziplogger/browser/react'
const zl = new ZipLoggerBrowser({ endpoint: 'https://logs...', apiKey: 'zk_...' })
zl.captureGlobalErrors() // window.onerror + unhandledrejection
const Boundary = createErrorBoundary(React, zl)
// <Boundary fallback={<p>Something went wrong.</p>}><App /></Boundary>
Every event carries url and userAgent; render errors include the React
component stack; a keepalive flush on pagehide means events survive tab
closes. Use a dedicated API key for browser traffic so it can be revoked independently.
Fluent Bit / Vector
For apps you can't modify, ship files and container output with a log shipper — both talk to the plain NDJSON endpoint.
Fluent Bit
[INPUT]
Name tail
Path /var/log/app/*.log
Tag app
# the tail input emits "log"; ZipLogger expects "message"
[FILTER]
Name modify
Match *
Rename log message
[OUTPUT]
Name http
Match *
Host logs.yourcompany.com
Port 443
TLS On
URI /ingest/v1/logs
Format json_lines
Json_date_key timestamp
Json_date_format iso8601
Header X-Api-Key zk_...
Vector
[sources.app]
type = "file"
include = ["/var/log/app/*.log"]
[transforms.shape]
type = "remap"
inputs = ["app"]
source = '''
.severity = "info"
.source = "legacy-app"
'''
[sinks.ziplogger]
type = "http"
inputs = ["shape"]
uri = "https://logs.yourcompany.com/ingest/v1/logs"
encoding.codec = "json"
framing.method = "newline_delimited"
request.headers.X-Api-Key = "zk_..."
.message already;
Fluent Bit's tail input uses log, hence the rename filter. Unknown JSON keys are
ignored by the ingestion endpoint, so extra metadata is harmless.OpenTelemetry
ZipLogger exposes a native OTLP/HTTP logs receiver at /v1/logs — protobuf and JSON,
gzip supported, partial success per the OTLP spec. Any OTel SDK or Collector can export to it:
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://logs.yourcompany.com
OTEL_EXPORTER_OTLP_HEADERS=X-Api-Key=zk_...
Resource attributes map to first-class fields: service.name → source,
service.version → release, deployment.environment, host.name,
and exception.stacktrace → the regression engine. Trace and span IDs are preserved
as searchable hex fields.
REST API
Ingestion
curl -X POST https://logs.yourcompany.com/ingest/v1/logs \
-H "X-Api-Key: zk_..." \
-H "Content-Type: application/json" \
-d '{"message":"deploy finished","severity":"info","source":"ci"}'
Accepts a single object, a JSON array, or NDJSON (one object per line). Fields:
timestamp, source, severity, message,
release, commitSha, stackTrace, fields{},
tags[] — everything optional except message.
Search
GET /api/v1/logs/search?q=payment+failed&severity=error&from=2026-07-01T00:00:00Z
Authorization: Bearer <jwt>
Additional endpoint groups: /api/v1/logs/histogram, /api/v1/templates
(patterns), /api/v1/dashboards, /api/v1/alerts,
/api/v1/metrics, /api/v1/regressions, and /api/v1/billing.
Quotas
When a daily or monthly quota is exhausted, ingestion answers 429 with a
Retry-After header pointing at the next UTC midnight, plus a JSON body with your
current usage. The official SDKs honor it automatically.
Authentication
Two credential types, used for different things:
| Credential | Used for | How |
|---|---|---|
API key (zk_…) | Log & metric ingestion, OTLP | X-Api-Key header |
| JWT | Everything else (search, dashboards, billing…) | Authorization: Bearer |
Sign in via POST /api/v1/auth/login to receive a short-lived access token and a
rotating refresh token; refresh with POST /api/v1/auth/refresh. Roles — Admin, Editor,
Viewer — gate destructive and administrative operations.
API keys
Create keys under Settings → API keys (or POST /api/v1/admin/api-keys). The
full key is shown exactly once at creation; only a SHA-256 hash is stored. Each key belongs to one
tenant, counts toward your plan's key limit, and can be revoked instantly. Rotate by creating a
new key, deploying it, then revoking the old one — zero downtime.
Configuration
Day-to-day configuration happens in the app, per workspace:
| Setting | Where |
|---|---|
| Ingestion API keys (create / rotate / revoke) | Settings → API keys |
| Team members and roles | Team |
| Plan, usage, invoices, payment method | Billing |
| Alert rules and notification webhooks | Alerts |
| Connected repositories for regression detection | Regressions |
| AI analysis (enabled per plan by your administrator) | Search → AI |
Server-level settings — TLS, backups, resource sizing, AI and payment credentials — are the administrator's one-time job and are covered in the operator deployment guide.
FAQ
Logs aren't showing up — what should I check?
1) The API key is sent as X-Api-Key and hasn't been revoked. 2) The endpoint is
reachable from your app (the SDKs retry silently — check DroppedCount).
3) You haven't exhausted your daily quota — the Billing page shows today's usage. 4) The
ingestion response: 202 accepted, 429 quota, 401 bad key.
Can I import logs with historical timestamps?
Yes — set timestamp on each event. They land in the right time buckets, count
toward today's ingestion quota, and age out based on their own timestamp.
How do I connect a repository for regression detection?
Under Regressions, add a name and the path of a repository clone on your ZipLogger server. Analysis runs entirely on that machine — your code never leaves your infrastructure.
Where does the AI send my data?
Only to the Anthropic API, only when you invoke an AI feature, and only a bounded sample of the window you're analyzing. No key configured → the features hide themselves.