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:
@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
MyLoggervia the constructor:
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.
// 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
- Use Primitive Types: Stick to simple types like strings, numbers, and booleans
- Keep it Concise: Don't log massive objects or sensitive information
- Be Consistent: Use similar key names across different log statements
- 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
// ✅ 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.