Structured Logging

Send structured logs from your application using the SDK logger.

Overview#

The JustAnalytics SDK provides a built-in structured logger that automatically correlates logs with traces and sends them to the Log Explorer.

Using the Logger#

Basic Usage#

import JA from '@justanalyticsapp/node';

JA.init({
  siteId: 'YOUR_SITE_ID',
  apiKey: 'YOUR_API_KEY',
  serviceName: 'api-server',
});

// Log at different levels
JA.logger.debug('Detailed debug information', { query: sql });
JA.logger.info('User logged in', { userId: 'user_123' });
JA.logger.warn('Slow query detected', { duration_ms: 2500, table: 'orders' });
JA.logger.error('Payment failed', { orderId: '456', reason: 'card_declined' });
JA.logger.fatal('Database connection lost', { host: 'db.example.com' });

Structured Context#

Always include structured data as the second argument:

// Good: structured context
JA.logger.info('Order processed', {
  orderId: 'ord_123',
  total: 149.99,
  items: 3,
  duration_ms: 245,
});

// Avoid: embedding data in the message
JA.logger.info('Order ord_123 processed for $149.99 with 3 items in 245ms');

Integration with Winston#

Use the Winston transport to send logs from existing Winston-based applications:

import winston from 'winston';
import { createWinstonTransport } from '@justanalyticsapp/node/winston';

const logger = winston.createLogger({
  level: 'info',
  transports: [
    new winston.transports.Console(),
    createWinstonTransport(), // Sends logs to JustAnalytics
  ],
});

logger.info('Server started', { port: 3000 });

Integration with Pino#

Use the Pino transport for Pino-based applications:

import pino from 'pino';
import { createPinoTransport } from '@justanalyticsapp/node/pino';

const logger = pino({
  transport: {
    targets: [
      { target: 'pino-pretty' },
      createPinoTransport(), // Sends logs to JustAnalytics
    ],
  },
});

logger.info({ userId: 'user_123' }, 'User logged in');

Automatic Trace Correlation#

When logs are emitted inside a startSpan callback, the trace ID and span ID are automatically attached:

JA.startSpan('handle-request', async (span) => {
  JA.logger.info('Request received', { path: '/api/orders' });
  // This log includes: traceId, spanId, serviceName

  const orders = await JA.startSpan('db.query', async () => {
    JA.logger.debug('Querying orders table');
    // This log includes the child span's ID
    return await db.orders.findMany();
  });

  JA.logger.info('Request completed', { orderCount: orders.length });
});

Log Format#

Logs sent to JustAnalytics follow this format:

{
  "timestamp": "2024-03-15T10:30:00.000Z",
  "level": "info",
  "message": "Order processed",
  "service": "api-server",
  "environment": "production",
  "traceId": "abc123def456",
  "spanId": "789ghi",
  "context": {
    "orderId": "ord_123",
    "total": 149.99
  }
}

Best Practices#

  1. Use structured context -- pass data as objects, not embedded in strings
  2. Use appropriate levels -- debug for verbose info, error for actual errors
  3. Include identifiers -- always include relevant IDs (userId, orderId, etc.)
  4. Be consistent -- use the same field names across your application
  5. Avoid sensitive data -- never log passwords, tokens, or PII