INFINI Console Audit Logging: How User Actions and System Events Are Tracked

INFINI Console captures every significant user action and system event by constructing a validated AuditLog object via a fluent builder, then asynchronously enqueuing the JSON payload to the logging-audit-log-queue for persistent storage without blocking the request path.

The infinilabs/console repository implements a comprehensive audit logging system that records who performed what action, when, and from where. This article examines the core components, validation logic, and asynchronous processing pipeline that ensure every audit entry is reliably persisted to Elasticsearch or other backends.

Core Components of INFINI Console Audit Logging

AuditLog Model and Builder

The foundation resides in model/audit_log.go, which defines the AuditLog struct and the AuditLogBuilder fluent API. The struct holds timestamps, operator identity, log type, resource type, and a flexible Labels map for event-specific metadata.

The builder pattern enforces required fields while supplying sensible defaults:

auditLog, _ := model.NewAuditLogBuilderWithDefault().
    WithOperator(claims.Username).
    WithLogTypeAccess().
    WithResourceTypeClusterManagement().
    WithEventName("monitoring " + eventName).
    WithEventSourceIP(common.GetClientIP(req)).
    WithResourceName(targetClusterID).
    WithOperationTypeAccess().
    WithEventRecord(req.URL.RawQuery).
    Build()

LogAuditLog Service

The service/audit_log.go file contains the LogAuditLog function, which serves as the primary entry point for persisting audit entries. This function validates the log structure before enqueueing:

func LogAuditLog(log *model.AuditLog) error {
    if err := log.Validate(); err != nil {
        return err
    }
    _, err := NewAuditLogAction(log).Execute()
    return err
}

The NewAuditLogAction(log).Execute() method serializes the struct to JSON using util.MustToJSONBytes and pushes it to the queue named logging-audit-log-queue.

Client IP Extraction

Located in common/audit_log.go, the GetClientIP helper extracts the client IP from HTTP headers, checking X-Forwarded-For, then X-Real-IP, and falling back to RemoteAddr to ensure accurate attribution of user actions.

The Audit Logging Flow in INFINI Console

The system follows a four-stage pipeline that decouples logging from request handling:

  1. Context Collection: Interceptors or API handlers extract user claims, resource IDs, and request metadata from the HTTP context.

  2. Log Construction: The AuditLogBuilder assembles the entry with mandatory fields including operator, log type, resource type, event name, source IP, and operation type.

  3. Validation and Enqueueing: The LogAuditLog service validates timestamps, mandatory fields, IP format, and operation type before serializing to JSON and pushing to logging-audit-log-queue.

  4. Asynchronous Persistence: A queue consumer (external to this repository) processes the JSON payload and persists it to Elasticsearch, ensuring the audit trail is searchable for compliance and troubleshooting.

Implementing Audit Logging in Practice

Generic Handler Implementation

When recording a login event, construct the log and fire the service call:

import (
    "infini.sh/console/common"
    "infini.sh/console/model"
    "infini.sh/console/service"
    "net/http"
)

func recordLogin(w http.ResponseWriter, r *http.Request, user *User) {
    auditLog, _ := model.NewAuditLogBuilderWithDefault().
        WithOperator(user.Username).
        WithLogTypeAccess().
        WithResourceTypeAccountCenter().
        WithEventName("user login").
        WithEventSourceIP(common.GetClientIP(r)).
        WithOperationTypeLogin().
        Build()

    _ = service.LogAuditLog(auditLog)
}

Monitoring Interceptor Pattern

The plugin/audit_log/monitoring_interceptor.go demonstrates automatic audit logging for monitoring API calls:

func (m *MonitoringInterceptor) PreHandle(ctx context.Context, _ http.ResponseWriter, req *http.Request) (context.Context, error) {
    claims, _ := security.ValidateLogin(req.Header.Get("Authorization"))
    if claims != nil && handler.GetHeader(req, "Referer", "") != "" {
        auditLog, _ := model.NewAuditLogBuilderWithDefault().
            WithOperator(claims.Username).
            WithLogTypeAccess().
            WithResourceTypeClusterManagement().
            WithEventName("monitoring " + eventName).
            WithEventSourceIP(common.GetClientIP(req)).
            WithResourceName(targetClusterID).
            WithOperationTypeAccess().
            WithEventRecord(req.URL.RawQuery).
            Build()
        _ = service.LogAuditLog(auditLog)
    }
    return ctx, nil
}

Extracting Client IP Addresses

Use the common utility to ensure accurate IP attribution across proxy layers:

ip := common.GetClientIP(req)

Summary

  • INFINI Console audit logging uses a builder pattern in model/audit_log.go to construct validated AuditLog objects with mandatory fields including operator, resource type, and source IP.
  • The LogAuditLog service in service/audit_log.go validates entries and asynchronously enqueues them to logging-audit-log-queue as JSON payloads.
  • Asynchronous processing decouples audit persistence from request handling, ensuring zero latency impact while guaranteeing reliable storage to Elasticsearch.
  • Interceptors like monitoring_interceptor.go automatically generate audit entries for API calls, while the GetClientIP helper ensures accurate attribution behind proxies.

Frequently Asked Questions

How does INFINI Console ensure audit logs are not lost if the system crashes?

The system uses a persistent message queue (logging-audit-log-queue) to decouple log generation from storage. When service.LogAuditLog is called, the JSON payload is pushed to the queue immediately. A separate consumer process (typically external to the Console application) drains this queue and persists entries to Elasticsearch. This design ensures that even if the Console service restarts, queued messages remain intact and will be processed once the system recovers.

What fields are mandatory when creating an audit log entry?

According to the validation logic in model/audit_log.go, every audit entry must include: operator (who performed the action), log type (e.g., access or system), resource type (the subsystem affected), event name (human-readable description), source IP (client address validated for format), and operation type (the specific action category). The AuditLogBuilder enforces these requirements during construction, and the Validate() method returns errors if any mandatory field is missing or malformed.

Can I customize which user actions generate audit logs?

Yes. While INFINI Console automatically logs many actions through interceptors like monitoring_interceptor.go, you can implement custom audit logging by invoking the service.LogAuditLog function directly within your API handlers or middleware. The AuditLogBuilder provides methods like WithLabels() to attach custom metadata, and you can define your own ResourceType or EventName values to categorize domain-specific actions. This flexibility allows you to capture business-critical events beyond the standard system-generated logs.

How does the system handle client IP detection behind reverse proxies?

The common.GetClientIP function in common/audit_log.go implements a hierarchical fallback strategy to accurately identify the original client address. It first checks the X-Forwarded-For header (common in proxy chains), then falls back to X-Real-IP (used by Nginx and similar proxies), and finally uses the connection's RemoteAddr if neither header is present. This ensures that audit logs correctly attribute actions to the actual end-user rather than internal proxy addresses, maintaining accurate compliance records even in complex network topologies.

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 →