Skip to content

Observability & Logging Guidelines ​

Welcome to the Agri Backend observability guide! πŸš€

Our application logs and metrics are sent to Elasticsearch and visualized in Kibana. We also use Elastic APM for distributed tracing and request correlation.

When working locally your logs & metrics can also be searchable. Sets the relevant environment variables and NODE_ENV=local.

Logging with NestJS ​

  • Use the custom logger (MyLogger) in your NestJS services/components:
typescript
@Injectable()
export class SomeService {
  private readonly logger: MyLogger = new MyLogger(SomeService.name);
  constructor() {}

  async doSomething(input: any) {
    const result = await this.compute(input);
    this.logger.log(
      {
        savedTimeMs: result,
        input: {
          questionsCpt: questionDocs.length,
          charactersCpt: characterDocs.length,
        },
      },
      `Time saved computed: ${JSON.stringify(result)}`,
    );
  }
}
  • Context attributes (the first argument to log) are indexed and searchable in Elasticsearch/Kibana.
  • The log message (second argument) is indexed as a string.

Using logger ​

  • Always inject MyLogger via the constructor:
typescript
private readonly logger = new MyLogger(Service.name)
constructor() {}
  • Add relevant context to your logs for better searchability and debugging.
  • Don't be crazy with context. ES is a paid service even if it can handle a lot we should try to avoid putting huge data there

Adding log context / searchable fields ​

Log context allows you to add structured metadata to your logs, making them more searchable and informative in the monitoring dashboard.

How to Add Context ​

When logging, you can pass an optional context object as the second argument. This object's attributes become indexed and searchable in Elasticsearch/Kibana.

typescript
// Basic logging with context
this.logger.log('User logged in', {
  userId: user.id,
  email: user.email,
  loginMethod: 'email',
  ipAddress: req.ip,
});

// Logging with error context
this.logger.error('Database connection failed', {
  errorCode: 'DB_CONN_001',
  connectionString: 'mongodb://...',
  retryAttempt: 3,
});

Best Practices for Log Context ​

  1. Use Primitive Types: Stick to simple types like strings, numbers, and booleans
  2. Keep it Concise: Don't log massive objects or sensitive information
  3. Be Consistent: Use similar key names across different log statements
  4. Include Relevant Details: Add context that helps in debugging or understanding the system state

Searchable Context Examples ​

  • User-related logs: userId, email, role
  • Performance metrics: executionTimeMs, resourceUsed
  • System events: serviceVersion, environmentName
  • Error tracking: errorCode, stackTrace

What to Avoid ​

  • Avoid logging sensitive data (passwords, tokens)
  • Don't log entire large objects
  • Keep the context object relatively small and meaningful
typescript
// βœ… Good: Concise, relevant context
this.logger.log('Processing user request', {
  requestId: uuid(),
  endpoint: '/api/users',
});

// ❌ Bad: Too much information, potential security risk
this.logger.log('User details', {
  user: JSON.stringify(userObject), // Avoid logging entire objects
  sensitiveData: user.password, // Never log sensitive information
});

Performance Considerations ​

While Elasticsearch can handle a lot of data, be mindful of the volume and complexity of your log contexts to manage costs and performance.