Skip to content

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=production

Structured 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();
});

Released under the MIT License.