Overview
LogFleet provides a simple HTTP API for sending logs and metrics. These examples show how to integrate LogFleet into your applications across different languages and frameworks.All examples assume you have a LogFleet edge agent running locally on port
8080. For production, replace with your agent’s address.JavaScript / TypeScript
Basic Log Shipping (Node.js)
Copy
import fetch from 'node-fetch';
interface LogEntry {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
service: string;
metadata?: Record<string, unknown>;
}
async function sendLog(entry: LogEntry): Promise<void> {
const response = await fetch('http://localhost:8080/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-LogFleet-Source': entry.service,
},
body: JSON.stringify({
timestamp: new Date().toISOString(),
...entry,
}),
});
if (!response.ok) {
throw new Error(`Failed to send log: ${response.statusText}`);
}
}
// Usage
await sendLog({
level: 'info',
message: 'Order processed successfully',
service: 'order-service',
metadata: {
orderId: '12345',
customerId: 'cust-789',
amount: 99.99,
},
});
Express.js Middleware
Copy
import { Request, Response, NextFunction } from 'express';
const LOGFLEET_ENDPOINT = process.env.LOGFLEET_ENDPOINT || 'http://localhost:8080/logs';
interface RequestLog {
method: string;
path: string;
statusCode: number;
duration: number;
userAgent?: string;
ip?: string;
}
export function logfleetMiddleware() {
return (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', async () => {
const log: RequestLog = {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: Date.now() - start,
userAgent: req.get('User-Agent'),
ip: req.ip,
};
// Fire and forget - don't block response
fetch(LOGFLEET_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
timestamp: new Date().toISOString(),
level: res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info',
message: `${req.method} ${req.path} ${res.statusCode}`,
service: 'api-gateway',
...log,
}),
}).catch(console.error);
});
next();
};
}
// Usage in Express app
import express from 'express';
const app = express();
app.use(logfleetMiddleware());
React Error Boundary
Copy
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
class LogFleetErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Send to LogFleet
fetch('/api/logfleet', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
level: 'error',
message: error.message,
service: 'web-app',
metadata: {
stack: error.stack,
componentStack: errorInfo.componentStack,
url: window.location.href,
userAgent: navigator.userAgent,
},
}),
}).catch(console.error);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default LogFleetErrorBoundary;
Python
Basic Logger
Copy
import requests
import json
from datetime import datetime
from typing import Optional, Dict, Any
import logging
class LogFleetHandler(logging.Handler):
"""Python logging handler that sends logs to LogFleet."""
def __init__(self, endpoint: str = "http://localhost:8080/logs", service: str = "python-app"):
super().__init__()
self.endpoint = endpoint
self.service = service
self.session = requests.Session()
def emit(self, record: logging.LogRecord) -> None:
try:
log_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname.lower(),
"message": self.format(record),
"service": self.service,
"metadata": {
"logger": record.name,
"filename": record.filename,
"lineno": record.lineno,
"funcName": record.funcName,
}
}
# Add exception info if present
if record.exc_info:
log_entry["metadata"]["exception"] = self.formatException(record.exc_info)
self.session.post(
self.endpoint,
json=log_entry,
headers={"Content-Type": "application/json"},
timeout=5
)
except Exception:
self.handleError(record)
# Usage
logger = logging.getLogger("my-app")
logger.addHandler(LogFleetHandler(service="order-processor"))
logger.setLevel(logging.INFO)
logger.info("Processing order", extra={"order_id": "12345"})
logger.error("Payment failed", exc_info=True)
FastAPI Integration
Copy
from fastapi import FastAPI, Request
from fastapi.middleware.base import BaseHTTPMiddleware
import time
import httpx
import asyncio
class LogFleetMiddleware(BaseHTTPMiddleware):
def __init__(self, app: FastAPI, endpoint: str = "http://localhost:8080/logs"):
super().__init__(app)
self.endpoint = endpoint
self.client = httpx.AsyncClient()
async def dispatch(self, request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = (time.perf_counter() - start) * 1000 # ms
# Fire and forget
asyncio.create_task(self._send_log(request, response, duration))
return response
async def _send_log(self, request: Request, response, duration: float):
try:
await self.client.post(
self.endpoint,
json={
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"level": "error" if response.status_code >= 500 else "info",
"message": f"{request.method} {request.url.path} {response.status_code}",
"service": "api",
"metadata": {
"method": request.method,
"path": str(request.url.path),
"status_code": response.status_code,
"duration_ms": round(duration, 2),
"client_ip": request.client.host if request.client else None,
}
},
timeout=5
)
except Exception:
pass # Don't fail requests due to logging
# Usage
app = FastAPI()
app.add_middleware(LogFleetMiddleware)
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
return {"order_id": order_id, "status": "completed"}
Django Middleware
Copy
# middleware.py
import time
import json
import threading
import requests
from django.conf import settings
class LogFleetMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.endpoint = getattr(settings, 'LOGFLEET_ENDPOINT', 'http://localhost:8080/logs')
self.service = getattr(settings, 'LOGFLEET_SERVICE', 'django-app')
def __call__(self, request):
start = time.time()
response = self.get_response(request)
duration = (time.time() - start) * 1000
# Send log in background thread
threading.Thread(
target=self._send_log,
args=(request, response, duration)
).start()
return response
def _send_log(self, request, response, duration):
try:
requests.post(
self.endpoint,
json={
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"level": "error" if response.status_code >= 500 else "info",
"message": f"{request.method} {request.path} {response.status_code}",
"service": self.service,
"metadata": {
"method": request.method,
"path": request.path,
"status_code": response.status_code,
"duration_ms": round(duration, 2),
"user_id": str(request.user.id) if request.user.is_authenticated else None,
}
},
timeout=5
)
except Exception:
pass
Go
Basic Client
Copy
package logfleet
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
type LogLevel string
const (
LevelDebug LogLevel = "debug"
LevelInfo LogLevel = "info"
LevelWarn LogLevel = "warn"
LevelError LogLevel = "error"
)
type LogEntry struct {
Timestamp string `json:"timestamp"`
Level LogLevel `json:"level"`
Message string `json:"message"`
Service string `json:"service"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type Client struct {
endpoint string
service string
httpClient *http.Client
}
func NewClient(endpoint, service string) *Client {
return &Client{
endpoint: endpoint,
service: service,
httpClient: &http.Client{
Timeout: 5 * time.Second,
},
}
}
func (c *Client) Log(ctx context.Context, level LogLevel, message string, metadata map[string]interface{}) error {
entry := LogEntry{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Level: level,
Message: message,
Service: c.service,
Metadata: metadata,
}
body, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("marshal log entry: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", c.endpoint, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
return nil
}
// Convenience methods
func (c *Client) Info(ctx context.Context, msg string, meta map[string]interface{}) error {
return c.Log(ctx, LevelInfo, msg, meta)
}
func (c *Client) Error(ctx context.Context, msg string, meta map[string]interface{}) error {
return c.Log(ctx, LevelError, msg, meta)
}
HTTP Middleware
Copy
package middleware
import (
"net/http"
"time"
"yourproject/logfleet"
)
func LogFleetMiddleware(client *logfleet.Client) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap response writer to capture status code
wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(wrapped, r)
duration := time.Since(start)
// Log asynchronously
go func() {
level := logfleet.LevelInfo
if wrapped.statusCode >= 500 {
level = logfleet.LevelError
} else if wrapped.statusCode >= 400 {
level = logfleet.LevelWarn
}
client.Log(r.Context(), level, r.Method+" "+r.URL.Path, map[string]interface{}{
"method": r.Method,
"path": r.URL.Path,
"status_code": wrapped.statusCode,
"duration_ms": duration.Milliseconds(),
"user_agent": r.UserAgent(),
"remote_addr": r.RemoteAddr,
})
}()
})
}
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
Usage with Chi Router
Copy
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"yourproject/logfleet"
"yourproject/middleware"
)
func main() {
client := logfleet.NewClient("http://localhost:8080/logs", "api-server")
r := chi.NewRouter()
r.Use(middleware.LogFleetMiddleware(client))
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
r.Get("/orders/{id}", func(w http.ResponseWriter, r *http.Request) {
orderID := chi.URLParam(r, "id")
// Business logic...
// Manual logging with context
client.Info(r.Context(), "Order retrieved", map[string]interface{}{
"order_id": orderID,
})
w.Write([]byte(`{"status": "found"}`))
})
http.ListenAndServe(":3000", r)
}
cURL / HTTP
Send a Log Entry
Copy
curl -X POST http://localhost:8080/logs \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-01-15T10:30:00Z",
"level": "info",
"message": "User logged in successfully",
"service": "auth-service",
"metadata": {
"user_id": "user-123",
"ip": "192.168.1.1",
"method": "oauth"
}
}'
Batch Send Logs
Copy
curl -X POST http://localhost:8080/logs/batch \
-H "Content-Type": "application/json" \
-d '{
"logs": [
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "info",
"message": "Request received",
"service": "api"
},
{
"timestamp": "2024-01-15T10:30:01Z",
"level": "info",
"message": "Request processed",
"service": "api"
}
]
}'
Query Logs via Loki (Local)
Copy
# Query last 100 logs from a service
curl -G "http://localhost:3100/loki/api/v1/query_range" \
--data-urlencode 'query={service="api"}' \
--data-urlencode 'limit=100' \
--data-urlencode 'start='$(date -u -v-1H +%s)000000000 \
--data-urlencode 'end='$(date -u +%s)000000000
# Query error logs only
curl -G "http://localhost:3100/loki/api/v1/query_range" \
--data-urlencode 'query={service="api"} |= "error"' \
--data-urlencode 'limit=50'
Vector Configuration
Basic HTTP Source
Copy
# vector.yaml
sources:
http_logs:
type: http_server
address: "0.0.0.0:8080"
encoding: json
path: /logs
transforms:
parse_logs:
type: remap
inputs: ["http_logs"]
source: |
# Ensure required fields
.timestamp = .timestamp ?? now()
.level = .level ?? "info"
.service = .service ?? "unknown"
# Add edge metadata
.edge_location = get_env_var("EDGE_LOCATION") ?? "unknown"
.edge_node = get_env_var("HOSTNAME") ?? "unknown"
sinks:
loki:
type: loki
inputs: ["parse_logs"]
endpoint: "http://loki:3100"
labels:
service: "{{ service }}"
level: "{{ level }}"
edge_location: "{{ edge_location }}"
encoding:
codec: json
Log-to-Metric Extraction
Copy
# Extract metrics from HTTP request logs
transforms:
http_metrics:
type: log_to_metric
inputs: ["parse_logs"]
metrics:
- type: counter
field: message
name: http_requests_total
tags:
service: "{{ service }}"
method: "{{ metadata.method }}"
status: "{{ metadata.status_code }}"
- type: histogram
field: metadata.duration_ms
name: http_request_duration_ms
tags:
service: "{{ service }}"
method: "{{ metadata.method }}"
sinks:
prometheus:
type: prometheus_exporter
inputs: ["http_metrics"]
address: "0.0.0.0:9090"
Error Rate Alerting
Copy
transforms:
error_counter:
type: log_to_metric
inputs: ["parse_logs"]
metrics:
- type: counter
field: level
name: log_errors_total
namespace: logfleet
tags:
service: "{{ service }}"
condition:
type: vrl
source: .level == "error"
# Calculate error rate (errors per minute)
error_rate:
type: aggregate
inputs: ["error_counter"]
interval_ms: 60000
mode: tumbling
Docker Compose Example
Full local development setup:Copy
version: '3.8'
services:
vector:
image: timberio/vector:latest-alpine
ports:
- "8080:8080" # Log ingestion
- "9090:9090" # Prometheus metrics
volumes:
- ./vector.yaml:/etc/vector/vector.yaml
environment:
- EDGE_LOCATION=store-001
depends_on:
- loki
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml
- loki-data:/loki
command: -config.file=/etc/loki/config.yaml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
volumes:
loki-data:
grafana-data:
Best Practices
Use structured logging
Use structured logging
Always send logs as structured JSON with consistent fields:
timestamp: ISO 8601 formatlevel: debug, info, warn, errormessage: Human-readable descriptionservice: Origin service namemetadata: Additional context as key-value pairs
Don't block on logging
Don't block on logging
Send logs asynchronously to avoid impacting request latency:
- Use fire-and-forget patterns
- Set reasonable timeouts (5 seconds max)
- Fail silently - don’t crash your app if logging fails
Add request context
Add request context
Include correlation IDs and request metadata for traceability:
- Request ID / Trace ID
- User ID (if authenticated)
- Session ID
- Client IP and User-Agent
Use appropriate log levels
Use appropriate log levels
debug: Detailed diagnostic informationinfo: Normal operations, request logswarn: Recoverable issues, deprecationserror: Failures requiring attention
Batch when possible
Batch when possible
For high-throughput services, batch logs to reduce network overhead:
- Buffer logs locally
- Flush on interval (e.g., every 5 seconds)
- Flush on threshold (e.g., every 100 logs)