Logging Practices Enforced in the Akash Console Project: A Complete Guide

The Akash Console project mandates structured logging through a centralized LoggerService that wraps pino, explicitly prohibiting all native console.* methods in favor of configurable, context-aware log objects that require an event field.

The akash-network/console repository enforces strict logging practices to ensure observability across its distributed deployment infrastructure. These practices are codified in repository guidelines and implemented through a custom logging package that standardizes output format, log levels, and contextual metadata injection. Understanding these enforcement mechanisms is essential for contributing to the codebase or deploying Console services in production environments.

Centralized LoggerService Architecture

The foundation of Akash Console logging is the LoggerService class defined in packages/logging/src/services/logger/logger.service.ts. This service wraps a pino instance and acts as the single sanctioned abstraction for all logging operations across the monorepo.

Pino-Based Implementation

LoggerService initializes its underlying pino logger by reading global configuration set during application bootstrap. The initialization logic (lines 75–77) retrieves the log level from the LOG_LEVEL environment variable, defaulting to "info":

private initPino(): pino.Logger {
  const options: pino.LoggerOptions = {
    level: LoggerService.config.LOG_LEVEL,
    mixin: LoggerService.mixin,
    // …
  };
  // …
}

The service exposes standard severity methods—log(), info(), error(), warn(), debug(), and fatal()—that accept either structured objects or Error instances. When receiving an error, the implementation automatically serializes it using the internal logError function (lines 176–197), enriching HTTP errors, SQL errors, and generic exceptions with stack traces and uniform metadata shapes.

Context Binding with forContext()

Every log line can carry a context field identifying the subsystem or component generating the message. The static forContext() method creates a logger instance with pre-bound context:

const dbLogger = LoggerService.forContext("Database");
dbLogger.debug({ event: "QUERY_EXECUTED", sql: "SELECT * FROM users" });

This implementation (lines 49–54 in logger.service.ts) attaches the context string to pino's bindings, ensuring every subsequent log entry includes the "context": "Database" field without manual repetition.

Configuration and Environment Variables

Logging behavior is controlled via environment variables defined in packages/logging/src/config/env.config.ts. The schema validates two critical settings:

LOG_LEVEL: z.enum(["fatal","error","warn","info","debug","trace"]).default("info"),
STD_OUT_LOG_FORMAT: z.enum(["json","pretty"]).default("json")

LOG_LEVEL determines the minimum severity emitted, while STD_OUT_LOG_FORMAT toggles between machine-readable JSON (ideal for aggregation pipelines) and human-readable pretty-printing. When "pretty" is selected in a Node.js environment, LoggerService attempts to dynamically import pino-pretty (lines 102–116), falling back silently to JSON if the module is unavailable.

Structured Logging Requirements

Akash Console enforces that log messages be structured objects rather than primitive strings. While string messages are technically accepted, the repository guideline explicitly discourages them.

The Event Field Convention

The definitive logging guideline located at .claude/instructions/use-logger-service-instead-of-console.md requires logs to include an event field. This field acts as a machine-readable identifier for filtering and alerting:

// ✅ Correct usage
this.logger.info({ event: "DEPLOYMENT_CREATED", deploymentId: id });

// ❌ Incorrect usage
console.log("Deployment created", id);

The guideline marks raw console.* usage as bad (lines 28–38), while the LoggerService pattern with explicit event fields is marked as good.

Error Serialization

When logging errors, the service normalizes different error types into a consistent structure. The error() method checks if the payload is an Error instance and wraps it accordingly:

error(message: LogMessage): void {
  if (message && message instanceof Error) {
    this.pino.error({ err: message });
  } else {
    this.pino.error(message);
  }
}

This ensures that stack traces, error codes, and messages appear in predictable JSON paths regardless of whether the error originated from HTTP requests, database queries, or application logic.

Dependency Injection Pattern

Services throughout the monorepo obtain loggers through constructor injection rather than instantiation. The provider files (e.g., apps/tx-signer/src/providers/logging.provider.ts) re-export LoggerService, enabling the dependency injection container to manage singleton instances:

@singleton()
export class DeploymentService {
  constructor(private readonly logger: LoggerService) {}

  async create() {
    try {
      // …deployment logic
      this.logger.info({ event: "DEPLOYMENT_CREATED", deploymentId: id });
    } catch (error) {
      this.logger.error({ event: "DEPLOYMENT_FAILED", error });
      throw error;
    }
  }
}

This pattern guarantees that the same configuration and mixins apply across all services within a request scope.

Extensibility via Mixins

The logging architecture supports mixins—functions that inject additional fields into every log line. This enables OpenTelemetry trace correlation and Sentry integration without polluting application code.

OTEL and Trace Data Injection

The createOtelLogger utility in packages/logging/src/utils/create-otel-logger/create-otel-logger.ts demonstrates mixing trace IDs into logs. Similarly, the web application logger in apps/stats-web/src/lib/createLogger/createLogger.ts merges Sentry baggage:

return new LoggerService({
  ...options,
  mixin: () => {
    const traceData = getTraceData();
    return { traceId: traceData["sentry-trace"], baggage: traceData.baggage };
  }
});

When using this logger, every log line automatically carries distributed tracing context required for correlating logs across microservices.

Enforcement Mechanisms

The prohibition against raw console.* methods is not merely conventional—it is codified in the repository's instruction set. The file .claude/instructions/use-logger-service-instead-of-console.md serves as a linting guideline and code review checklist, explicitly banning console.log, console.warn, console.error, and console.info.

Violations are caught during code review by searching for these patterns, and the guideline document provides concrete good and bad examples to clarify expectations. All new code must import LoggerService from either @akashnetwork/logging or @src/core depending on the package location within the monorepo.

Summary

  • All logging must use LoggerService from @akashnetwork/logging or @src/core; native console.* methods are explicitly prohibited per repository guidelines.
  • Logs must be structured objects containing an event field for filtering, with optional context, error, and custom metadata fields.
  • Configuration is environment-driven via LOG_LEVEL (severity threshold) and STD_OUT_LOG_FORMAT (JSON vs. pretty-print).
  • Contextual logging is achieved through LoggerService.forContext() or constructor injection, automatically attaching component identifiers to every line.
  • Extensibility comes through mixin functions that inject OpenTelemetry trace data or Sentry baggage without manual field passing.
  • Error handling is standardized through the internal logError serializer, ensuring uniform error shapes across HTTP, SQL, and application exceptions.

Frequently Asked Questions

What happens if I use console.log in the Akash Console codebase?

Using console.log, console.error, or any native console method violates the repository guideline defined in .claude/instructions/use-logger-service-instead-of-console.md. Code containing these calls will fail code review and must be refactored to use LoggerService with a structured object containing an event field.

How do I configure the log level for different environments?

Set the LOG_LEVEL environment variable to one of the supported values: "fatal", "error", "warn", "info", "debug", or "trace". This is validated in packages/logging/src/config/env.config.ts and applied when LoggerService initializes its pino instance. The default is "info" if the variable is unset.

Can I add custom fields to every log line automatically?

Yes, supply a mixin function during LoggerService configuration or instantiation. This function should return an object containing the fields you want appended to all logs. The createOtelLogger and createLogger utilities demonstrate this pattern for injecting trace IDs and Sentry baggage.

Is pretty-printed output available for local development?

Yes, set STD_OUT_LOG_FORMAT=pretty in your environment. When running in Node.js, LoggerService will attempt to load pino-pretty for human-readable output. If the module is not installed, it falls back to JSON format without crashing the application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →