Skip to main content

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)

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

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

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

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

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

# 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

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

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

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

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

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)

# 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

# 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

# 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

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:
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

Always send logs as structured JSON with consistent fields:
  • timestamp: ISO 8601 format
  • level: debug, info, warn, error
  • message: Human-readable description
  • service: Origin service name
  • metadata: Additional context as key-value pairs
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
Include correlation IDs and request metadata for traceability:
  • Request ID / Trace ID
  • User ID (if authenticated)
  • Session ID
  • Client IP and User-Agent
  • debug: Detailed diagnostic information
  • info: Normal operations, request logs
  • warn: Recoverable issues, deprecations
  • error: Failures requiring attention
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)

Next Steps