Skip to content

Logs API

The Logs API provides endpoints for searching, filtering, and retrieving container logs from Meilisearch.

Search logs with full-text search and filters.

GET /api/logs/search
ParameterTypeRequiredDescription
queryStringNoFull-text search term
containerStringNoContainer name filter
streamStringNostdout or stderr
fromTimestampNumberNoStart time (Unix ms)
toTimestampNumberNoEnd time (Unix ms)
limitNumberNoMax results (default: 50, max: 100)
offsetNumberNoSkip N results (default: 0)
Terminal window
curl "http://localhost:3000/api/logs/search?query=error&container=nginx&limit=20"
{
"success": true,
"total": 145,
"returned": 20,
"processingTimeMs": 12,
"logs": [
{
"id": "abc123-1705761234567-xyz",
"containerId": "abc123def456",
"containerName": "nginx",
"timestamp": 1705761234567,
"timestampISO": "2026-01-20T14:23:54.567Z",
"message": "Connection timeout to upstream server",
"stream": "stderr",
"labels": {
"loggator.enable": "true",
"app": "web"
}
}
]
}

Get a list of containers with log counts.

GET /api/logs/containers
Terminal window
curl "http://localhost:3000/api/logs/containers"
{
"success": true,
"totalCount": 1523,
"containers": [
{
"id": "abc123def456",
"name": "nginx",
"count": 845
},
{
"id": "def456ghi789",
"name": "api",
"count": 523
},
{
"id": "ghi789jkl012",
"name": "database",
"count": 155
}
]
}

Get log count histogram over time.

GET /api/logs/histogram
ParameterTypeRequiredDescription
minutesNumberNoTime window (default: 60)
containerStringNoFilter by container
Terminal window
curl "http://localhost:3000/api/logs/histogram?minutes=120&container=nginx"
{
"success": true,
"buckets": [
{
"minute": "2026-01-20T12:00:00Z",
"count": 45,
"timestamp": 1705752000000
},
{
"minute": "2026-01-20T12:01:00Z",
"count": 52,
"timestamp": 1705752060000
}
],
"total": 2547,
"timeWindow": "120 minutes"
}

Find logs containing “error”:

Terminal window
curl "http://localhost:3000/api/logs/search?query=error"

Logs from specific container:

Terminal window
curl "http://localhost:3000/api/logs/search?container=nginx"

Only stderr logs:

Terminal window
curl "http://localhost:3000/api/logs/search?stream=stderr"

Logs from last hour:

Terminal window
NOW=$(date +%s)000
HOUR_AGO=$((NOW - 3600000))
curl "http://localhost:3000/api/logs/search?fromTimestamp=$HOUR_AGO&toTimestamp=$NOW"

Error logs from nginx’s stderr in the last 2 hours:

Terminal window
NOW=$(date +%s)000
TWO_HOURS_AGO=$((NOW - 7200000))
curl "http://localhost:3000/api/logs/search?query=error&container=nginx&stream=stderr&fromTimestamp=$TWO_HOURS_AGO"

Get results 20-40:

Terminal window
curl "http://localhost:3000/api/logs/search?query=error&limit=20&offset=20"
interface LogSearchParams {
query?: string;
container?: string;
stream?: "stdout" | "stderr";
fromTimestamp?: number;
toTimestamp?: number;
limit?: number;
offset?: number;
}
async function searchLogs(params: LogSearchParams) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
searchParams.append(key, value.toString());
}
});
const response = await fetch(
`http://localhost:3000/api/logs/search?${searchParams}`,
);
return await response.json();
}
// Usage
const logs = await searchLogs({
query: "error",
container: "nginx",
limit: 50,
});
console.log(`Found ${logs.total} logs, showing ${logs.returned}`);
logs.logs.forEach((log) => {
console.log(`[${log.timestampISO}] ${log.containerName}: ${log.message}`);
});
import requests
from datetime import datetime, timedelta
from typing import Optional
def search_logs(
query: Optional[str] = None,
container: Optional[str] = None,
stream: Optional[str] = None,
from_timestamp: Optional[int] = None,
to_timestamp: Optional[int] = None,
limit: int = 50,
offset: int = 0
):
params = {
'limit': limit,
'offset': offset
}
if query:
params['query'] = query
if container:
params['container'] = container
if stream:
params['stream'] = stream
if from_timestamp:
params['fromTimestamp'] = from_timestamp
if to_timestamp:
params['toTimestamp'] = to_timestamp
response = requests.get(
'http://localhost:3000/api/logs/search',
params=params
)
return response.json()
# Usage - errors from last hour
one_hour_ago = int((datetime.now() - timedelta(hours=1)).timestamp() * 1000)
logs = search_logs(
query='error',
from_timestamp=one_hour_ago,
limit=100
)
print(f"Found {logs['total']} errors")
for log in logs['logs']:
print(f"{log['timestamp']}: {log['message']}")
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
type LogSearchParams struct {
Query string
Container string
Stream string
FromTimestamp int64
ToTimestamp int64
Limit int
Offset int
}
type LogSearchResponse struct {
Success bool `json:"success"`
Total int `json:"total"`
Logs []Log `json:"logs"`
}
type Log struct {
ID string `json:"id"`
ContainerName string `json:"containerName"`
Message string `json:"message"`
Stream string `json:"stream"`
TimestampISO string `json:"timestampISO"`
Labels map[string]string `json:"labels"`
}
func searchLogs(params LogSearchParams) (*LogSearchResponse, error) {
baseURL := "http://localhost:3000/api/logs/search"
queryParams := url.Values{}
if params.Query != "" {
queryParams.Add("query", params.Query)
}
if params.Container != "" {
queryParams.Add("container", params.Container)
}
if params.Limit > 0 {
queryParams.Add("limit", fmt.Sprintf("%d", params.Limit))
}
resp, err := http.Get(baseURL + "?" + queryParams.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result LogSearchResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
// Usage
func main() {
logs, err := searchLogs(LogSearchParams{
Query: "error",
Container: "nginx",
Limit: 50,
})
if err != nil {
panic(err)
}
fmt.Printf("Found %d logs\n", logs.Total)
for _, log := range logs.Logs {
fmt.Printf("[%s] %s: %s\n",
log.TimestampISO,
log.ContainerName,
log.Message)
}
}
#!/bin/bash
# Search function
search_logs() {
local query=$1
local container=$2
local limit=${3:-50}
local url="http://localhost:3000/api/logs/search"
local params="limit=$limit"
if [ -n "$query" ]; then
params="$params&query=$query"
fi
if [ -n "$container" ]; then
params="$params&container=$container"
fi
curl -s "$url?$params" | jq '.logs[] | "\(.timestampISO) [\(.containerName)] \(.message)"'
}
# Usage
search_logs "error" "nginx" 20

Invalid parameters:

{
"success": false,
"error": "Invalid parameter: limit must be between 1 and 100"
}

Server error:

{
"success": false,
"error": "Meilisearch connection failed"
}

Request only what you need:

  • limit=1000 (slow, may timeout)
  • limit=50 (fast, paginate if needed)

Reduce search scope:

Terminal window
# Last hour only
fromTimestamp=$(($(date +%s) - 3600))000

Search specific containers:

Terminal window
container=nginx

Cache results when appropriate:

const cache = new Map();
async function getCachedLogs(params) {
const key = JSON.stringify(params);
if (cache.has(key)) {
return cache.get(key);
}
const logs = await searchLogs(params);
cache.set(key, logs);
return logs;
}

For large result sets:

async function getAllLogs(query) {
const allLogs = [];
let offset = 0;
const limit = 100;
while (true) {
const response = await searchLogs({ query, limit, offset });
allLogs.push(...response.logs);
if (response.returned < limit) {
break; // No more results
}
offset += limit;
}
return allLogs;
}