@walsys/cloudflare_worker-gelf_logger
v1.0.5
Published
Production-ready GELF logger for Cloudflare Workers with automatic context extraction
Downloads
692
Maintainers
Readme
GELF Logger for Cloudflare Workers
A production-ready GELF (Graylog Extended Log Format) logger specifically designed for Cloudflare Workers. Non-blocking, lightweight, and packed with Cloudflare-specific integrations.
Features
- ✅ Full GELF 1.1 Specification Compliance - Complete support for Graylog Extended Log Format
- 🚀 Non-Blocking Logging - Fire-and-forget design that never blocks your worker
- 🌐 Cloudflare Native - Automatic extraction of Cloudflare-specific context
- 📊 8 Log Levels - Emergency, Alert, Critical, Error, Warning, Notice, Info, Debug
- 🔧 Zero Configuration - Opinionated defaults that just work
- 📍 Geo-Location Support - Automatic latitude/longitude from Cloudflare request
- 🏢 Colo Tracking - Know which Cloudflare data center handled the request
- 🎯 Custom Fields - Add unlimited custom fields to any log
- 🔄 Context-Aware - Child loggers preserve parent context
- 🆔 Session Tracking - Unique session ID for tracing multi-instance executions
- 🔍 Extended Request Details - Captures path, method, user agent, and more
- 📈 Built-in Statistics - Track sent, failed, and skipped logs
- 🛡️ Graceful Failure - Logging failures never crash your worker
Installation
npm install @walsys/cloudflare_worker-gelf_loggerOr with Yarn:
yarn add @walsys/cloudflare_worker-gelf_loggerQuick Start
import { GELFLogger } from '@walsys/cloudflare_worker-gelf_logger';
export default {
async fetch(request, env, ctx) {
// Initialize logger with env and request
const logger = new GELFLogger({ env, request });
// Log at different levels
logger.info('User login successful');
logger.error('Database connection failed');
logger.debug('Cache miss for key: user-123');
// Ensure logs are sent before worker terminates
ctx.waitUntil(logger.flush());
return new Response('OK');
}
};Environment Variables
The logger automatically consumes these Cloudflare Worker environment variables:
| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| GELF_LOGGING_URL | GELF HTTP endpoint URL | Yes | - |
| WORKER_NAME | Worker name (used as host identifier) | No | 'cloudflare-worker' |
| ENVIRONMENT | Environment name (e.g., production, staging) | No | Not logged if absent |
| FUNCTION_NAME | Function/route name | No | Not logged if absent |
| LOG_SESSION_ID | Session ID for message segmentation | No | Generated UUID |
Setting Environment Variables
In your wrangler.toml or wrangler.jsonc:
[vars]
GELF_LOGGING_URL = "https://your-graylog-server.com/gelf"
WORKER_NAME = "api-gateway"
ENVIRONMENT = "production"
FUNCTION_NAME = "user-authentication"Configuration Options
const logger = new GELFLogger({
// Required: Cloudflare env object
env: env,
// Optional: Cloudflare Request object (extracts colo, IP, geo data)
request: request,
// Optional: Override GELF endpoint
endpoint: 'https://custom-graylog.com/gelf',
// Optional: Override host identifier
host: 'custom-worker-name',
// Optional: Session ID (defaults to env.LOG_SESSION_ID or generated UUID)
log_session_id: 'custom-session-uuid',
// Optional: Facility name
facility: 'worker',
// Optional: Global fields added to all logs
globalFields: {
app_version: '1.2.3',
team: 'backend'
},
// Optional: Minimum log level (default: INFO)
minLevel: GELFLogger.LEVELS.DEBUG,
// Optional: Also log to console (default: true)
consoleLog: true,
// Optional: Request timeout in milliseconds (default: 5000)
timeout: 3000,
// Optional: Max failed messages to track (default: 50)
maxFailedMessages: 100
});Automatic Cloudflare Context
When you pass the request object, the logger automatically extracts and includes:
_colo- Cloudflare data center code (e.g., "SFO", "LHR")_client_ip- Client's IP address (fromcf-connecting-iporx-forwarded-for)_longitude- Client's longitude (fromrequest.cf.longitude)_latitude- Client's latitude (fromrequest.cf.latitude)_country- Client's country_city- Client's city_region- Client's region_asn- Client's ASN_as_organization- Client's AS Organization_request_path- URL path_request_host- URL hostname_request_method- HTTP method_request_id- Request ID (fromcf-rayorx-request-id)_user_agent- User Agent string
These fields are only included if they exist - no null values are logged.
Log Levels
Based on Syslog severity levels:
logger.emergency('System is down'); // Level 0 - System unusable
logger.alert('Immediate action required'); // Level 1 - Take action immediately
logger.critical('Critical database error'); // Level 2 - Critical conditions
logger.error('Failed to process payment'); // Level 3 - Error conditions
logger.warning('High memory usage'); // Level 4 - Warning conditions
logger.notice('Config file reloaded'); // Level 5 - Normal but significant
logger.info('User logged in'); // Level 6 - Informational
logger.debug('Cache lookup: user-123'); // Level 7 - Debug messages
// Aliases
logger.warn('Same as warning'); // Alias for warning
logger.log('Same as info'); // Alias for infoUsage Examples
Basic Logging
logger.info('User authenticated successfully');
logger.error('Payment processing failed', null, {
user_id: '12345',
amount: 99.99,
payment_method: 'stripe'
});Logging with Full Message
logger.warning(
'High memory usage detected',
'Memory usage is above 80% threshold. Consider scaling.',
{ memory_percent: 85, threshold: 80 }
);Exception Logging
try {
await processPayment();
} catch (error) {
logger.exception(error, {
user_id: userId,
context: 'payment-processing'
});
}Context-Aware Logging (Child Loggers)
// Create a child logger with additional context
const requestLogger = logger.child({
request_id: crypto.randomUUID(),
user_id: '12345'
});
// All logs from this child include request_id and user_id
requestLogger.info('Processing user request');
requestLogger.debug('Fetching user data from database');Global Context (Avoid Prop-Drilling)
You can "hoist" the logger into a global context using logger.run(). This uses AsyncLocalStorage to make the logger available anywhere in your call stack without passing it as an argument.
// worker.js
import { GELFLogger } from '@walsys/cloudflare_worker-gelf_logger';
import { handleUserRequest } from './handlers';
export default {
async fetch(request, env, ctx) {
const logger = new GELFLogger({ env, request });
// Run the entire request handling within the logger context
return logger.run(async () => {
// Now you can access the logger anywhere without passing it
return await handleUserRequest(request);
});
}
};
// handlers.js
import { GELFLogger } from '@walsys/cloudflare_worker-gelf_logger';
export async function handleUserRequest(request) {
// Retrieve the current logger instance
const logger = GELFLogger.current;
if (logger) {
logger.info('Handling user request from global context');
}
return new Response('OK');
}Custom Fields
logger.info('API request completed', null, {
method: 'POST',
path: '/api/users',
status_code: 201,
duration_ms: 145,
user_agent: request.headers.get('user-agent')
});Conditional Logging Based on Level
// Only logs at INFO level and below
const logger = new GELFLogger({
env,
minLevel: GELFLogger.LEVELS.INFO
});
logger.debug('This will be skipped'); // Skipped
logger.info('This will be logged'); // LoggedAPI Reference
Constructor
new GELFLogger(config)
Creates a new logger instance.
Logging Methods
emergency(shortMessage, fullMessage?, customFields?)- Level 0alert(shortMessage, fullMessage?, customFields?)- Level 1critical(shortMessage, fullMessage?, customFields?)- Level 2error(shortMessage, fullMessage?, customFields?)- Level 3warning(shortMessage, fullMessage?, customFields?)- Level 4warn(shortMessage, fullMessage?, customFields?)- Alias for warningnotice(shortMessage, fullMessage?, customFields?)- Level 5info(shortMessage, fullMessage?, customFields?)- Level 6log(shortMessage, fullMessage?, customFields?)- Alias for infodebug(shortMessage, fullMessage?, customFields?)- Level 7
Exception Logging
exception(error, customFields?)
Logs an error object with automatic stack trace extraction.
Utility Methods
child(contextFields)
Creates a child logger with additional context fields.
const childLogger = logger.child({ request_id: '12345' });run(callback)
Runs a callback function within the context of this logger instance.
logger.run(() => {
// GELFLogger.current will return this logger inside here
someDeeplyNestedFunction();
});static get current
Retrieves the current logger instance from the async context.
const logger = GELFLogger.current;async flush()
Waits for all pending logs to be sent. Use with ctx.waitUntil().
ctx.waitUntil(logger.flush());getStats()
Returns logging statistics.
const stats = logger.getStats();
// { sent: 150, failed: 2, skipped: 10, failedMessagesCount: 2 }getFailedMessages(limit?)
Returns failed log messages for debugging.
const failures = logger.getFailedMessages(5); // Last 5 failuresgetFailureSummary()
Returns summary of failure reasons.
const summary = logger.getFailureSummary();
// { no_endpoint: 0, http_error: 1, timeout: 1, network_error: 0, other: 0 }clearFailedMessages()
Clears the failed messages history.
resetStats()
Resets all statistics and failed messages.
Best Practices
1. Always Use ctx.waitUntil()
Ensure logs are sent before your worker terminates:
export default {
async fetch(request, env, ctx) {
const logger = new GELFLogger({ env, request });
// Your code here
logger.info('Processing request');
// Critical: Ensure logs are sent
ctx.waitUntil(logger.flush());
return new Response('OK');
}
};2. Use Child Loggers for Request Context
Create child loggers per request for better tracing:
const requestLogger = logger.child({
request_id: crypto.randomUUID(),
path: new URL(request.url).pathname
});3. Set Appropriate Log Levels for Production
Use INFO or WARNING in production to reduce noise:
const logger = new GELFLogger({
env,
minLevel: env.ENVIRONMENT === 'production'
? GELFLogger.LEVELS.INFO
: GELFLogger.LEVELS.DEBUG
});4. Include Relevant Context in Custom Fields
logger.error('Database query failed', null, {
query: 'SELECT * FROM users',
table: 'users',
error_code: 'ER_NO_SUCH_TABLE',
duration_ms: 150
});5. Use Exception Logging for Errors
try {
await riskyOperation();
} catch (error) {
logger.exception(error, { operation: 'user-update' });
throw error; // Re-throw if needed
}Cloudflare-Specific Features
Automatic Colo Detection
Know which Cloudflare data center handled each request:
const logger = new GELFLogger({ env, request });
// Logs automatically include _colo field (e.g., "SFO", "LAX", "LHR")Geolocation Logging
Track where your users are located:
const logger = new GELFLogger({ env, request });
// Logs include _latitude and _longitude from request.cfClient IP Tracking
Automatically logs the client's real IP:
const logger = new GELFLogger({ env, request });
// Logs include _client_ip from cf-connecting-ip headerPerformance Considerations
- Non-Blocking: Logging never blocks your request handling
- Fire-and-Forget: Logs are sent asynchronously
- Failed Message Tracking: Limited to 50 messages by default to prevent memory issues
- Timeout: Default 5-second timeout prevents hanging requests
Troubleshooting
No logs appearing in Graylog?
- Check that
GELF_LOGGING_URLis set correctly - Verify your Graylog server is accepting GELF HTTP input
- Check failed messages:
logger.getFailedMessages() - Ensure you're calling
ctx.waitUntil(logger.flush())
High failure rate?
const stats = logger.getStats();
const summary = logger.getFailureSummary();
console.log('Stats:', stats);
console.log('Failure reasons:', summary);Logs not appearing for certain levels?
Check your minLevel setting. By default, only INFO and above are logged:
const logger = new GELFLogger({
env,
minLevel: GELFLogger.LEVELS.DEBUG // Include debug logs
});GELF Message Format
Each log message follows the GELF 1.1 specification:
{
"version": "1.1",
"host": "my-worker",
"short_message": "User login successful",
"timestamp": 1634567890.123,
"level": 6,
"facility": "worker",
"_environment": "production",
"_function_name": "auth-handler",
"_colo": "SFO",
"_client_ip": "203.0.113.42",
"_latitude": 37.7749,
"_longitude": -122.4194,
"_custom_field": "value"
}License
MIT © [Your Name]
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
- GitHub Issues: https://github.com/walsys/cloudflare_worker-gelf_logger/issues
- Documentation: https://github.com/walsys/cloudflare_worker-gelf_logger
Made with ❤️ for Cloudflare Workers
