LokiStore — Grafana Loki log aggregation
Read this page in the documentation
LokiStore — Grafana Loki log aggregation Overview Grafana Loki is a horizontally-scalable, multi-tenant log aggregation engine. It indexes only a small set of labels per log stream (not the log contents) and is queried with LogQL, a label-matcher + filter language modelled on PromQL. There is no SQL-shaped query surface, no row-level UPDATE/DELETE, and no identifier escaping, so Loki does not fit the SQL Dialect interface (src/dialects/dialect.ts). Because of that, LokiStore implements the minimal NoSqlStore marker interface (src/nosql/store.ts) — connection lifecycle plus a getClient() escape hatch — and exposes Loki's HTTP push/query API directly (log ingestion via the push endpoint, LogQL range search, and label/series metadata lookups). Identity: Property | Value | --------- | --------- | name | 'loki' | library | 'fetch' | No canonical driver — HTTP over fetch Loki has no single canonical npm client; it is just an HTTP/JSON API. Rather than depend on any package, this store talks to the server with the global fetch (so library is 'fetch'). When no client is injected, a tiny internal client over fetch + baseURL is built at connect() time by createFetchClient(baseURL, headers). Injected client LokiStoreOptions accepts a pre-built client implementing the LokiHttpClient interface. When provided it is used verbatim and baseURL is ignored — this is how the test suite injects a mock (no network) and how callers can supply their own transport. Connection Build a store from connection options and call connect(): Option | Type | Purpose | ---------- | ------------------------- | ----------------------------------------------------------------------- | baseURL | string | Base URL of the Loki server. Defaults to http://localhost:3100. | client | LokiHttpClient | Pre-built HTTP client. When set, baseURL is ignored. | headers | Record<string, string> | Extra HTTP headers sent with every internal-client request. | tenantID | string | Optional tenant ID, merged into headers as X-Scope-OrgID. | Injected-client form Methods Every read method routes through an internal doGet, which unwraps the response's data field and wraps any client failure in a DatabaseError. Calling a method before connect() (or after disconnect()) throws a ConnectionError. Lifecycle Method | Signature | Behavior | -------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- | connect | connect(): Promise<void> | Uses an injected client if provided, otherwise builds a fetch client from baseURL. Idempotent. | disconnect | disconnect(): Promise<void> | Clears the client and connection state. | isConnected | isConnected(): boolean | true only when connected and a client is present. | getClient | getClient(): LokiHttpClient | Returns the underlying (internal or injected) client. Throws ConnectionError if not connected. | Ingest / query / metadata Method | Signature | Endpoint | Behavior | ------------- | ------------------------------------------------------------------------------------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------- | push | push(labels: Record<string, string>, entries: LokiEntry[]): Promise<any> | POST /loki/api/v1/push | Pushes a batch of log entries for one stream (identified by its label set). Each entry serializes to [<ts-ns>, <line>], plus optional structured metadata as a third tuple element. | query | query(logql: string, options?: LokiQueryOptions): Promise<any> | GET /loki/api/v1/queryrange | Runs a LogQL query over a time window. Returns the data payload ({ resultType, result }). | labels | labels(): Promise<any> | GET /loki/api/v1/labels | Lists the known label names (the response data). | streams | streams(match?: string \| string[]): Promise<any> | GET /loki/api/v1/series | Lists the label sets (streams) matching an optional selector, sent as match[]. | labelValues | labelValues(label: string): Promise<any> | GET /loki/api/v1/label/<name>/values | Lists the distinct values of a label (name URL-encoded). | Option / entry shapes Example Verification status Unit / mock-verified only. The tests in tests/nosql/loki.test.ts are fully mock-driven: an in-memory LokiHttpClient that records every request (path + params/body) and returns canned Loki response envelopes is injected via LokiStoreOptions.client. There is no live Loki server and no network in the test run. What this proves: Each method targets the correct endpoint with the expected params/body (e.g. push builds the { streams: [{ stream, values }] } payload with the [ts, line] / [ts, line, metadata] tuples; query stringifies its options; streams sends match[]). query/labels unwrap the response data field. Lifecycle: idempotent connect(), getClient() returning the injected client, and ConnectionError before connect(). Error handling: client failures wrapped in DatabaseError. What this does not prove: live execution against a real Grafana Loki deployment. Request/response shapes are verified against Loki's documented HTTP contract, but end-to-end execution over the wire has not been exercised here. Related reading All data stores — the full catalogue, grouped by purpose Database types — where this sits among the 22 categories