Any Logger JSON HTTP

A JSON HTTP appender extension for Any Logger that enables sending structured logs to REST APIs, Logstash, centralized logging services, and custom HTTP endpoints with automatic batching, retry logic, and compression support.

Features

  • Automatic Batching - Intelligently batches logs to reduce network overhead
  • Retry Logic - Configurable retry with exponential backoff for failed sends
  • Multiple Auth Methods - Supports Bearer tokens, Basic auth, and custom headers
  • Compression Support - Optional batch compression to reduce bandwidth
  • Flexible Configuration - Works with config files, builders, or programmatic setup
  • Error Recovery - Buffers logs during network failures and retries when connection resumes
  • Statistics Tracking - Monitor successful/failed sends and buffer status

Installation

dependencies:
  any_logger: ^x.y.z  
  any_logger_json_http: ^x.y.z  # See Installing

To register the JSON HTTP appender you have to import the library

import 'package:any_logger/any_logger.dart';
import 'package:any_logger_json_http/any_logger_json_http.dart';

and call:

AnyLoggerJsonHttpExtension.register();

Quick Start

Simple Setup

await LoggerFactory.builder()
  .console(level: Level.INFO)
  .jsonHttp(
    url: 'https://logs.example.com/api/logs',
    level: Level.WARN,
    authToken: 'your-api-key',
  )
  .build();

Logger.info('This goes to console only');
Logger.warn('This goes to both console and HTTP endpoint');
Logger.error('Errors are batched and sent efficiently');

With Authentication

// Bearer token (most common)
await LoggerFactory.builder()
  .jsonHttp(
    url: 'https://api.logservice.com',
    authToken: 'sk-1234567890',
    authType: 'Bearer',
    level: Level.ERROR,
  )
  .build();

// Basic authentication
await LoggerFactory.builder()
  .jsonHttp(
    url: 'https://logs.example.com',
    username: 'logger',
    password: 'secure_password',
    level: Level.INFO,
  )
  .build();

Configuration Options

Using Builder Pattern

final appender = await jsonHttpAppenderBuilder('https://logs.example.com')
  .withLevel(Level.ERROR)
  .withBearerToken('api-key-123')
  .withBatchSize(100)
  .withBatchIntervalSeconds(30)
  .withHeaders({
    'X-Application': 'MyApp',
    'X-Environment': 'production',
  })
  .withStackTraces(true)
  .withMetadata(true)
  .withMaxRetries(5)
  .withExponentialBackoff(true)
  .build();

Using Configuration Map

final config = {
  'appenders': [
    {
      'type': 'JSON_HTTP',
      'url': 'https://logs.example.com',
      'endpointPath': 'v2/ingest',
      'authToken': 'your-token',
      'authType': 'Bearer',
      'level': 'INFO',
      'batchSize': 100,
      'batchIntervalSeconds': 30,
      'headers': {
        'X-Application-Id': 'myapp',
        'X-Environment': 'production',
      },
      'includeMetadata': true,
      'includeStackTrace': true,
      'maxRetries': 3,
      'exponentialBackoff': true,
      'timeoutSeconds': 10,
    }
  ]
};

await LoggerFactory.init(config);

Configuration Parameters

Parameter Type Default Description
url String Required Base URL of the logging endpoint
endpointPath String null Optional path to append to URL
level Level INFO Minimum log level to send
authToken String null Authentication token
authType String 'Bearer' Type of auth ('Bearer', 'Basic', etc.)
username String null Username for Basic auth
password String null Password for Basic auth
batchSize int 100 Number of logs before sending
batchIntervalSeconds int 30 Max seconds before sending partial batch
headers Map {} Custom HTTP headers
includeMetadata bool true Include device/session/app info
includeStackTrace bool true Include stack traces for errors
maxRetries int 3 Number of retry attempts
exponentialBackoff bool true Use exponential backoff for retries
timeoutSeconds int 30 HTTP request timeout
compressBatch bool false Compress batched logs

Alternative Field Names

For compatibility with different configuration formats:

  • bufferSize β†’ batchSize
  • flushIntervalSeconds β†’ batchIntervalSeconds
  • enableCompression β†’ compressBatch

JSON Payload Structure

The appender sends logs in this format:

{
  "timestamp": "2025-01-20T10:30:45.123Z",
  "count": 3,
  "metadata": {
    "appVersion": "1.2.3",
    "deviceId": "a3f5c8d2",
    "sessionId": "e7b9f1a4",
    "hostname": "server-01"
  },
  "logs": [
    {
      "timestamp": "2025-01-20T10:30:45.100Z",
      "level": "ERROR",
      "levelValue": 40000,
      "message": "Database connection failed",
      "logger": "DatabaseService",
      "tag": "DB",
      "class": "DatabaseService",
      "method": "connect",
      "line": 42,
      "error": {
        "message": "Connection timeout",
        "type": "TimeoutException"
      },
      "stackTrace": "...",
      "mdc": {
        "userId": "user-123",
        "requestId": "req-456"
      }
    }
  ]
}

Presets

Logstash Integration

final appender = await jsonHttpAppenderBuilder('https://logstash.example.com')
  .withLogstashPreset() // Optimized for Logstash
  .build();

// Preset configures:
// - batchSize: 200
// - batchIntervalSeconds: 30
// - includeMetadata: true
// - includeStackTrace: true
// - exponentialBackoff: true

High Volume Logging

final appender = await jsonHttpAppenderBuilder('https://logs.example.com')
  .withHighVolumePreset() // Optimized for high throughput
  .build();

// Preset configures:
// - batchSize: 500
// - batchIntervalSeconds: 10
// - includeStackTrace: false (performance)
// - includeMetadata: false (reduce payload)
// - compressBatch: true

Critical Errors Only

final appender = await jsonHttpAppenderBuilder('https://alerts.example.com')
  .withCriticalErrorPreset()
  .withBearerToken('alert-api-key')
  .build();

// Preset configures:
// - level: ERROR
// - batchSize: 10 (send quickly)
// - batchIntervalSeconds: 5
// - includeStackTrace: true
// - maxRetries: 5

Integration Examples

With Centralized Logging Service

// Datadog, Loggly, Papertrail, etc.
await LoggerFactory.builder()
  .console()
  .jsonHttp(
    url: 'https://http-intake.logs.datadoghq.com',
    endpointPath: 'v1/input',
    authToken: process.env['DD_API_KEY'],
    headers: {
      'DD-EVP-ORIGIN': 'my-app',
      'DD-EVP-ORIGIN-VERSION': '1.0.0',
    },
    level: Level.INFO,
  )
  .build();

With Custom Backend

await LoggerFactory.builder()
  .jsonHttp(
    url: 'https://api.mycompany.com',
    endpointPath: 'logging/v1/ingest',
    authToken: await getAuthToken(),
    headers: {
      'X-Client-Version': '2.1.0',
      'X-Platform': Platform.operatingSystem,
    },
    batchSize: 50,
    includeMetadata: true,
  )
  .build();

Error Alerting System

// Send only errors immediately to alerting system
await LoggerFactory.builder()
  .file(filePattern: 'app', level: Level.DEBUG) // All logs to file
  .jsonHttp(
    url: 'https://alerts.example.com/critical',
    level: Level.ERROR, // Only errors to HTTP
    batchSize: 1, // Send immediately
    authToken: 'alert-key',
  )
  .build();

Monitoring & Statistics

final appender = logger.appenders
    .whereType<JsonHttpAppender>()
    .firstOrNull;

if (appender != null) {
  final stats = appender.getStatistics();
  print('Successful sends: ${stats['successfulSends']}');
  print('Failed sends: ${stats['failedSends']}');
  print('Buffer size: ${stats['bufferSize']}');
  print('Last send: ${stats['lastSendTime']}');
}

Best Practices

1. Choose Appropriate Batch Sizes

  • Low volume: 50-100 logs per batch
  • High volume: 200-500 logs per batch
  • Critical errors: 1-10 for immediate sending

2. Set Reasonable Intervals

  • Production: 30-60 seconds
  • Development: 5-10 seconds
  • Critical systems: 1-5 seconds

3. Handle Network Failures

// Configure retry strategy
.withMaxRetries(5)
.withExponentialBackoff(true)
.withTimeoutSeconds(10)

4. Optimize Payload Size

// For high-volume, non-critical logs
.withStackTraces(false)  // Reduce size
.withMetadata(false)      // Only if not needed
.withCompression(true)    // Compress batches

5. Secure Your Credentials

// Use environment variables or secure storage
final apiKey = Platform.environment['LOG_API_KEY'];
// Or use a secrets manager
final apiKey = await SecretManager.getSecret('log-api-key');

Troubleshooting

Logs Not Reaching Server

  1. Check network connectivity
  2. Verify authentication: Ensure token/credentials are correct
  3. Check URL and endpoint: Verify the full URL is correct
  4. Enable self-debugging:
await LoggerFactory.builder()
  .jsonHttp(url: 'https://logs.example.com')
  .withSelfDebug(Level.DEBUG)
  .build();

High Memory Usage

  • Reduce batchSize to limit buffer memory
  • Enable compressBatch for large payloads
  • Disable includeStackTrace if not needed

Logs Lost During Shutdown

Always flush before app termination:

// In your app shutdown handler
await LoggerFactory.flushAll();

Testing

For unit tests, use test mode to avoid actual HTTP calls:

final appender = await jsonHttpAppenderBuilder('https://test.example.com')
  .withLevel(Level.INFO)
  .build(test: true); // No actual HTTP calls made

License

MIT License - see LICENSE file for details.

Support


Part of the Any Logger ecosystem.

πŸ’š Funding


Happy Logging! πŸŽ‰