Best Practices
Production-ready logging requires more than just sending logs. This guide covers battle-tested practices for using Logstyx effectively in real-world applications.
Initialization
Initialize Once at Application Root
Create a single logger instance and reuse it throughout your application.
Node.js/Express:
javascript
// logger.js
const useLogstyx = require('logstyx-js-node');
const logger = useLogstyx({
projectId: process.env.LOGSTYX_PROJECT_ID,
apiKey: process.env.LOGSTYX_API_KEY,
endpoint: process.env.LOGSTYX_ENDPOINT,
captureUncaught: process.env.NODE_ENV === 'production',
captureUnhandledRejections: process.env.NODE_ENV === 'production'
});
module.exports = logger;
// app.js
const logger = require('./logger');
logger.info({ message: 'Application started' });React:
jsx
// App.jsx
import useLogstyx from 'logstyx-js-react';
function App() {
const logger = useLogstyx({
projectId: process.env.REACT_APP_LOGSTYX_PROJECT_ID,
endpoint: process.env.REACT_APP_LOGSTYX_ENDPOINT,
captureUncaught: process.env.NODE_ENV === 'production'
});
const { ErrorBoundary } = logger;
return (
<ErrorBoundary logger={logger}>
<Router>
{/* Your app */}
</Router>
</ErrorBoundary>
);
}React Native:
jsx
// App.jsx
import logstyx from 'logstyx-js-react-native';
import { __DEV__ } from 'react-native';
function App() {
const logger = logstyx({
projectId: process.env.LOGSTYX_PROJECT_ID,
appid: 'com.yourcompany.app',
endpoint: process.env.LOGSTYX_ENDPOINT,
captureUncaught: !__DEV__,
captureUnhandledRejections: !__DEV__
});
const { ErrorBoundary } = logger;
return (
<ErrorBoundary logger={logger}>
<NavigationContainer>
{/* Your app */}
</NavigationContainer>
</ErrorBoundary>
);
}Use Environment Variables
Never hardcode credentials or configuration:
javascript
// ✅ Good: Environment variables
const logger = useLogstyx({
projectId: process.env.LOGSTYX_PROJECT_ID,
apiKey: process.env.LOGSTYX_API_KEY,
endpoint: process.env.LOGSTYX_ENDPOINT
});
// ❌ Bad: Hardcoded values
const logger = useLogstyx({
projectId: 'proj_abc123',
apiKey: 'secret_key_xyz',
endpoint: 'https://logs.example.com/api/v1/logs'
});.env file:
bash
LOGSTYX_PROJECT_ID=proj_abc123xyz
LOGSTYX_API_KEY=your_api_key_here
LOGSTYX_ENDPOINT=https://logs.yourcompany.com/api/v1/logs
NODE_ENV=productionStructured Logging
Always Use Structured Data
Send structured objects, not plain strings:
javascript
// ❌ Avoid: Plain strings
logger.info('User john@example.com logged in');
logger.error('Payment failed for order 12345');
// ✅ Better: Structured data
logger.info({
message: 'User logged in',
email: 'john@example.com',
method: 'oauth2'
});
logger.error({
message: 'Payment failed',
orderId: '12345',
error: 'card_declined',
amount: 99.99
});Why? Structured data is:
- Searchable by specific fields
- Easier to filter and analyze
- More consistent across logs
- Better for reporting and alerts
Use Consistent Field Names
Standardize field names across your application:
javascript
// ✅ Good: Consistent naming
logger.info({ userId: '123', action: 'login' });
logger.info({ userId: '456', action: 'logout' });
logger.info({ userId: '789', action: 'purchase' });
// ❌ Avoid: Inconsistent naming
logger.info({ user_id: '123', action: 'login' });
logger.info({ userId: '456', event: 'logout' });
logger.info({ uid: '789', action_type: 'purchase' });Create a style guide:
javascript
// logging-style.js
const FIELDS = {
USER_ID: 'userId',
ORDER_ID: 'orderId',
TRANSACTION_ID: 'transactionId',
ERROR: 'error',
DURATION: 'duration',
STATUS: 'status'
};
// Usage
logger.info({
message: 'Order processed',
[FIELDS.USER_ID]: '123',
[FIELDS.ORDER_ID]: 'ord_456',
[FIELDS.DURATION]: 1500
});Include Relevant Context
Add context that helps with debugging:
javascript
// ❌ Not helpful
logger.error({ message: 'Something went wrong' });
// ✅ Helpful
logger.error({
message: 'Payment API request failed',
endpoint: '/v1/charges',
method: 'POST',
statusCode: 429,
error: 'Rate limit exceeded',
retryAfter: 60,
orderId: 'ord_123',
userId: 'user_456',
requestId: 'req_xyz'
});Context Management
Set Context Early, Clear Context Always
javascript
// ✅ Good: Set early, clear in finally
async function processOrder(order) {
logger.setContext({
orderId: order.id,
userId: order.userId
});
try {
logger.info({ message: 'Processing order started' });
await processPayment(order);
logger.info({ message: 'Order completed' });
} catch (error) {
logger.error({ message: 'Order failed', error: error.message });
throw error;
} finally {
logger.clearContext(['orderId', 'userId']);
}
}
// ❌ Bad: No cleanup
async function processOrder(order) {
logger.setContext({ orderId: order.id });
await processPayment(order);
// Context leaks to next operation!
}Use Context for Repeated Data
javascript
// ✅ Good: Context for repeated data
logger.setContext({ requestId: 'req_123', userId: 'user_456' });
logger.info({ message: 'Request received' });
logger.info({ message: 'Validation passed' });
logger.info({ message: 'Processing started' });
logger.info({ message: 'Response sent' });
logger.clearContext(['requestId', 'userId']);
// ❌ Avoid: Repeating in every log
logger.info({ message: 'Request received', requestId: 'req_123', userId: 'user_456' });
logger.info({ message: 'Validation passed', requestId: 'req_123', userId: 'user_456' });
logger.info({ message: 'Processing started', requestId: 'req_123', userId: 'user_456' });
logger.info({ message: 'Response sent', requestId: 'req_123', userId: 'user_456' });React: Always Use Cleanup
jsx
// ✅ Good: Cleanup on unmount
useEffect(() => {
logger.setContext({
component: 'ProductList',
page: 'catalog'
});
return () => logger.clearContext(['component', 'page']);
}, []);
// ❌ Bad: No cleanup
useEffect(() => {
logger.setContext({ component: 'ProductList' });
}, []);Error Handling
Log Errors with Full Context
javascript
// ❌ Minimal context
try {
await processPayment(order);
} catch (error) {
logger.error({ message: error.message });
}
// ✅ Full context
try {
await processPayment(order);
} catch (error) {
logger.error({
message: 'Payment processing failed',
error: error.message,
stack: error.stack,
orderId: order.id,
amount: order.total,
provider: 'stripe',
retryable: error.retryable
});
}Use Appropriate Log Levels
javascript
// ✅ Good: Correct levels
try {
const result = await apiCall();
logger.info({ message: 'API call successful' });
} catch (error) {
if (error.retryable) {
logger.warning({
message: 'API call failed, will retry',
error: error.message
});
} else {
logger.error({
message: 'API call failed permanently',
error: error.message
});
}
}
// ❌ Bad: Everything is error
logger.error({ message: 'User logged in' });
logger.error({ message: 'API call successful' });Enable Error Capture in Production
javascript
const logger = useLogstyx({
projectId: process.env.LOGSTYX_PROJECT_ID,
apiKey: process.env.LOGSTYX_API_KEY,
// Only in production
captureUncaught: process.env.NODE_ENV === 'production',
captureUnhandledRejections: process.env.NODE_ENV === 'production'
});Security
Never Log Sensitive Data
javascript
// ❌ NEVER do this
logger.info({
message: 'User registered',
password: user.password, // NO!
creditCard: user.creditCard, // NO!
ssn: user.ssn, // NO!
apiKey: config.apiKey // NO!
});
// ✅ Log safely
logger.info({
message: 'User registered',
userId: user.id,
email: user.email,
method: 'email_password'
});Sanitize User Input
javascript
// ✅ Good: Sanitize before logging
logger.info({
message: 'Search query',
query: sanitize(req.query.search),
results: results.length
});
function sanitize(input) {
if (typeof input !== 'string') return '';
return input
.slice(0, 100) // Limit length
.replace(/[^\w\s-]/g, '') // Remove special chars
.trim();
}Don't Log Tokens or Keys
javascript
// ❌ Never log tokens
logger.info({
message: 'API request',
authorization: req.headers.authorization, // NO!
apiKey: req.query.apiKey // NO!
});
// ✅ Log token metadata instead
logger.info({
message: 'API request',
authenticated: !!req.headers.authorization,
authMethod: 'bearer'
});Performance
Avoid Logging in Hot Paths
javascript
// ❌ Bad: Logging in tight loop
for (let i = 0; i < 10000; i++) {
logger.info({ message: 'Processing item', index: i }); // Too much!
}
// ✅ Better: Log summary
const results = [];
for (let i = 0; i < 10000; i++) {
results.push(processItem(i));
}
logger.info({
message: 'Batch processing completed',
processed: results.length,
successful: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length
});Sample High-Volume Logs
javascript
// ✅ Good: Sample in high-traffic scenarios
function logRequest(req) {
// Only log 10% of requests
if (Math.random() < 0.1) {
logger.info({
message: 'Request',
method: req.method,
path: req.path
});
}
}
// Or sample based on environment
function logRequest(req) {
const shouldLog = process.env.NODE_ENV !== 'production'
|| Math.random() < 0.1;
if (shouldLog) {
logger.info({ message: 'Request', method: req.method });
}
}Use Async Logging
The SDK is non-blocking, but avoid waiting for logs:
javascript
// ✅ Good: Fire and forget
logger.info({ message: 'User action' });
await processAction();
// ❌ Avoid: Waiting for logs (SDK doesn't require this anyway)
await logger.info({ message: 'User action' });
await processAction();Production Patterns
Add Application Metadata
Include version, environment, and deployment info:
javascript
// On app start
logger.setContext({
environment: process.env.NODE_ENV,
version: process.env.APP_VERSION,
deployment: process.env.DEPLOYMENT_ID,
region: process.env.AWS_REGION,
hostname: require('os').hostname()
});
logger.info({ message: 'Application started' });Track Important Events
javascript
// Application lifecycle
logger.info({ message: 'Application started', port: 3000 });
logger.info({ message: 'Database connected' });
logger.info({ message: 'Cache initialized' });
// Business events
logger.info({
message: 'User signup',
userId: user.id,
plan: user.subscription,
source: req.query.utm_source
});
logger.info({
message: 'Purchase completed',
orderId: order.id,
amount: order.total,
items: order.items.length
});
// Performance metrics
logger.info({
message: 'API response time',
endpoint: '/api/users',
duration: 145,
cached: false
});Implement Request ID Tracking
javascript
// Express middleware
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || generateId();
logger.setContext({ requestId: req.id });
res.on('finish', () => {
logger.info({
message: 'Request completed',
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: Date.now() - req.startTime
});
logger.clearContext(['requestId']);
});
next();
});Use Correlation IDs
Track related operations across services:
javascript
async function processOrder(order, correlationId) {
logger.setContext({
correlationId: correlationId || generateId(),
orderId: order.id
});
try {
logger.info({ message: 'Order processing started' });
// Pass correlationId to other services
await inventoryService.reserve(order.items, { correlationId });
await paymentService.charge(order.total, { correlationId });
logger.info({ message: 'Order completed' });
} finally {
logger.clearContext(['correlationId', 'orderId']);
}
}Testing
Mock Logger in Tests
javascript
// test-utils.js
export const mockLogger = {
info: jest.fn(),
warning: jest.fn(),
error: jest.fn(),
critical: jest.fn(),
setContext: jest.fn(),
clearContext: jest.fn()
};
// component.test.js
import { mockLogger } from './test-utils';
test('logs user action', () => {
const component = render(<Component logger={mockLogger} />);
fireEvent.click(screen.getByText('Submit'));
expect(mockLogger.info).toHaveBeenCalledWith({
message: 'Form submitted',
formType: 'contact'
});
});Don't Test Logs Excessively
javascript
// ❌ Avoid: Testing every log
test('processes order', async () => {
await processOrder(order);
expect(logger.info).toHaveBeenCalledWith({ message: 'Order started' });
expect(logger.info).toHaveBeenCalledWith({ message: 'Payment processed' });
expect(logger.info).toHaveBeenCalledWith({ message: 'Order completed' });
// Too brittle!
});
// ✅ Better: Test behavior, not logs
test('processes order', async () => {
const result = await processOrder(order);
expect(result.status).toBe('completed');
expect(result.paymentId).toBeDefined();
});Common Mistakes to Avoid
1. Logging Too Much
javascript
// ❌ Over-logging
function calculateTotal(items) {
logger.info({ message: 'Starting calculation' });
let total = 0;
for (const item of items) {
logger.info({ message: 'Processing item', item }); // Too much!
total += item.price;
}
logger.info({ message: 'Calculation done', total });
return total;
}
// ✅ Just right
function calculateTotal(items) {
const total = items.reduce((sum, item) => sum + item.price, 0);
logger.info({
message: 'Order total calculated',
items: items.length,
total
});
return total;
}2. Logging Too Little
javascript
// ❌ Under-logging
async function processPayment(order) {
try {
return await stripe.charge(order.total);
} catch (error) {
throw error; // No logging!
}
}
// ✅ Adequate logging
async function processPayment(order) {
logger.info({
message: 'Payment processing started',
orderId: order.id,
amount: order.total
});
try {
const result = await stripe.charge(order.total);
logger.info({
message: 'Payment processed',
chargeId: result.id
});
return result;
} catch (error) {
logger.error({
message: 'Payment failed',
error: error.message,
orderId: order.id
});
throw error;
}
}3. Inconsistent Structure
javascript
// ❌ Inconsistent
logger.info({ msg: 'User login', user: '123' });
logger.info({ message: 'User logout', userId: '123' });
logger.info({ text: 'User action', id: '123' });
// ✅ Consistent
logger.info({ message: 'User login', userId: '123' });
logger.info({ message: 'User logout', userId: '123' });
logger.info({ message: 'User action', userId: '123' });4. Forgetting to Clear Context
javascript
// ❌ Context leaks
async function handleRequest(req) {
logger.setContext({ requestId: req.id });
await processRequest(req);
// Next request will have wrong requestId!
}
// ✅ Always clear
async function handleRequest(req) {
logger.setContext({ requestId: req.id });
try {
await processRequest(req);
} finally {
logger.clearContext(['requestId']);
}
}5. Using Wrong Log Levels
javascript
// ❌ Wrong levels
logger.error({ message: 'User logged in' }); // Not an error!
logger.info({ message: 'Database connection lost' }); // Not just info!
// ✅ Correct levels
logger.info({ message: 'User logged in' });
logger.critical({ message: 'Database connection lost' });Monitoring and Alerts
Set Up Alerts for Critical Events
Configure alerts in your Logstyx dashboard:
javascript
// Log events that should trigger alerts
logger.critical({
message: 'Service outage',
service: 'payment-api',
impact: 'All payments blocked',
alert: true // Flag for alerting system
});
logger.error({
message: 'High error rate',
service: 'authentication',
rate: 15,
threshold: 10,
alert: true
});Track Key Metrics
javascript
// Response time tracking
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
message: 'Request completed',
endpoint: req.path,
method: req.method,
statusCode: res.statusCode,
duration,
slow: duration > 1000 // Flag slow requests
});
});
next();
});