ui

package
v3.6.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2026 License: AGPL-3.0 Imports: 30 Imported by: 0

README

Loki UI Architecture in Distributed Mode

Overview

Loki's UI system is designed to work seamlessly in a distributed environment. Select a node/deployment type to run the UI by including the ui target. This node will be the main entry point for the UI APIs, and can proxy requests to other nodes in the cluster.

Key Components

1. Node Discovery and Clustering
  • Each node advertises itself and maintains a list of peers (using dskit rings)
  • Nodes can join and leave the cluster dynamically
2. UI Service Components

The UI is served by a combination of a Grafan pluing and an API Layer hosted by the Loki node running the UI target. Note all Loki pods will join the UI ring, so that the UI node can proxy requests to them when necesary.

  • Grafana Plugin: Loki Operational UI plugin for Grafana
  • API Layer: REST endpoints for cluster state and proxying
  • Proxy System: Allows forwarding requests to specific nodes
  • Service Discovery: Tracks available nodes and their services

Architecture Diagram

graph TB
    LB[Reverse Proxy /ui/]

    subgraph Cluster[Loki Cluster]
        subgraph GrafanaNode[Grafana]
            UI1[Loki Operational UI Plugin]
        end

        subgraph Node1[Node 1]
            API1[API Server]
            PROXY1[Proxy Handler]
        end

        subgraph Node2[Node 2]
            API2[API Server]
            PROXY2[Proxy Handler]
        end

        subgraph Node3[Node 3]
            API3[API Server]
            PROXY3[Proxy Handler]
        end
    end
    
    GrafanaNode --> LB
    LB --> Node1
    LB --> Node2
    LB --> Node3

API Endpoints

All endpoints are prefixed with /ui/

Cluster Management
  • GET /ui/api/v1/cluster/nodes

    • Returns the state of all nodes in the cluster
    • Response includes node status, services, and build information
  • GET /ui/api/v1/cluster/nodes/{nodename}/details

    • Returns detailed information about the specified node
    • Includes configuration, analytics, and system information
Proxy System
  • GET /ui/api/v1/proxy/{nodename}/*
    • Proxies requests to specific nodes in the cluster
    • Maintains original request path after the node name
Analytics
  • GET /ui/api/v1/analytics
    • Returns analytics data for the node

Request Flow Examples

Example 1: Viewing Cluster Status
  1. User accesses UI via their configured Grafana instance
  2. Frontend loads and makes request to /ui/api/v1/cluster/nodes
  3. Node handling the request:
    • Queries all peers using ckit
    • Collects status from each node
    • Returns consolidated cluster state
Grafana->Node 1: GET /ui/api/v1/cluster/nodes
Node 1->Node 2: Fetch status
Node 1->Node 3: Fetch status
Node 2-->Node 1: Status response
Node 3-->Node 1: Status response
Node 1-->Browser: Combined cluster state
Example 2: Accessing Node-Specific Service
  1. User requests service data from specific node
  2. Frontend makes request to /ui/api/v1/proxy/{nodename}/services
  3. Request is proxied to target node
  4. Response returns directly to client
Grafana->Node 1: GET /ui/api/v1/proxy/node2/services
Node 1->Node 2: Proxy request
Node 2-->Node 1: Service data
Node 1-->Browser: Proxied response

Configuration

The UI service can be enabled by setting ui.enabled to true in the Loki config file.

ui:
    enabled: <bool>             # Enable the UI service

This will enable the UI ring, and allow the UI target to serve the API. Every node must have this configured to join the ring so the API can communicate with the whole cluster, while only one node needs the specify the ui target. Please note enabling the UI ring via this config does not start the UI target, that must be specified separately.

Security Considerations

  1. The UI endpoints should be protected behind authentication
  2. The /ui/ prefix allows easy reverse proxy configuration
  3. Node-to-node communication should be restricted to internal network

High Availability

  • Any node running the UI target can serve the UI API
  • Nodes automatically discover each other
  • Load balancer can distribute traffic across nodes

Best Practices

  1. Configure a reverse proxy in front of the Loki cluster
  2. Use consistent node names across the cluster
  3. Monitor cluster state for node health
  4. Use internal network for node-to-node communication

Concrete Example: Ring UI via Querier

Ring UI Overview

The Ring UI is a critical component for understanding the state of Loki's distributed hash ring. Here's how it works when accessed through a querier node. Since each ingester maintains the complete ring state, querying a single ingester is sufficient to view the entire ring:

Component Interaction
sequenceDiagram
    participant Grafana
    participant Querier Node
    participant Ingester

    Grafana->>Querier Node: GET /ui/api/v1/cluster/nodes
    Querier Node-->>Grafana: List of available nodes

    Note over Grafana,Querier Node: User clicks on Ring view

    Grafana->>Querier Node: GET /ui/api/v1/proxy/querier-1/ring

    Note over Querier Node: Querier fetches ring state

    Querier Node->>Ingester: Get ring status
    Ingester-->>Querier Node: Complete ring state

    Querier Node-->>Grafana: Ring state
Request Flow Details
  1. Initial UI Load

    GET /ui/api/v1/cluster/nodes
    
    • Frontend loads and discovers available nodes
    • UI shows querier node in the node list
  2. Ring State Request

    GET /ui/api/v1/proxy/querier-1/ring
    
    • Frontend requests ring state through the proxy endpoint
    • Request is forwarded to the querier's ring endpoint
    • Querier gets complete ring state from a single ingester
  3. Ring Data Structure

    {
      "tokens": [
        {
          "token": "123456",
          "ingester": "ingester-1",
          "state": "ACTIVE",
          "timestamp": "2024-02-04T12:00:00Z"
        },
        // ... more tokens
      ],
      "ingesters": {
        "ingester-1": {
          "state": "ACTIVE",
          "tokens": ["123456", "789012"],
          "address": "ingester-1:3100",
          "last_heartbeat": "2024-02-04T12:00:00Z"
        }
        // ... more ingesters
      }
    }
    
Security Notes
  1. Ring access should be restricted to authorized users
  2. Internal ring communication uses HTTP/2
  3. Ring state contains sensitive cluster information

Documentation

Overview

Package ui provides HTTP handlers for the Loki UI and cluster management interface.

Index

Constants

This section is empty.

Variables

View Source
var ErrGoldfishDisabled = sql.ErrNoRows

ErrGoldfishDisabled is returned when goldfish feature is disabled

View Source
var ErrGoldfishNotConfigured = sql.ErrConnDone

ErrGoldfishNotConfigured is returned when goldfish database is not configured

Functions

This section is empty.

Types

type BuildInfo

type BuildInfo struct {
	Version   string `json:"version"`
	Revision  string `json:"revision"`
	Branch    string `json:"branch"`
	BuildUser string `json:"buildUser"`
	BuildDate string `json:"buildDate"`
	GoVersion string `json:"goVersion"`
}

BuildInfo contains version and build information about a member.

type Cluster

type Cluster struct {
	Members map[string]Member `json:"members"`
}

Cluster represents a collection of cluster members.

type ComparisonOutcome added in v3.6.0

type ComparisonOutcome struct {
	CorrelationID      string    `json:"correlationId" db:"correlation_id"`
	ComparisonStatus   string    `json:"comparisonStatus" db:"comparison_status"`
	DifferenceDetails  any       `json:"differenceDetails" db:"difference_details"`
	PerformanceMetrics any       `json:"performanceMetrics" db:"performance_metrics"`
	ComparedAt         time.Time `json:"comparedAt" db:"compared_at"`
	CreatedAt          time.Time `json:"createdAt" db:"created_at"`
}

ComparisonOutcome represents a comparison result from the database

type Config

type Config struct {
	Enabled  bool                `yaml:"enabled"` // Whether to enable the UI.
	Debug    bool                `yaml:"debug"`
	Goldfish GoldfishConfig      `yaml:"goldfish"` // Goldfish query comparison configuration
	Ring     lokiring.RingConfig `yaml:"ring"`     // UI ring configuration for cluster member discovery
}

func (*Config) RegisterFlags

func (cfg *Config) RegisterFlags(f *flag.FlagSet)

type GoldfishAPIResponse added in v3.6.0

type GoldfishAPIResponse struct {
	Queries  []SampledQuery `json:"queries"`
	HasMore  bool           `json:"hasMore"`
	Page     int            `json:"page"`
	PageSize int            `json:"pageSize"`
}

GoldfishAPIResponse represents the paginated API response

type GoldfishConfig added in v3.6.0

type GoldfishConfig struct {
	Enable              bool   `yaml:"enable"`                // Whether to enable the Goldfish query comparison feature.
	CloudSQLUser        string `yaml:"cloudsql_user"`         // CloudSQL username
	CloudSQLHost        string `yaml:"cloudsql_host"`         // CloudSQL host
	CloudSQLPort        int    `yaml:"cloudsql_port"`         // CloudSQL port
	CloudSQLDatabase    string `yaml:"cloudsql_database"`     // CloudSQL database name
	MaxConnections      int    `yaml:"max_connections"`       // Maximum number of database connections
	MaxIdleTime         int    `yaml:"max_idle_time"`         // Maximum idle time for connections in seconds
	GrafanaURL          string `yaml:"grafana_url"`           // Base URL of Grafana instance for explore links
	TracesDatasourceUID string `yaml:"traces_datasource_uid"` // UID of the traces datasource in Grafana
	LogsDatasourceUID   string `yaml:"logs_datasource_uid"`   // UID of the Loki datasource in Grafana
	CellANamespace      string `yaml:"cell_a_namespace"`      // Namespace for Cell A logs
	CellBNamespace      string `yaml:"cell_b_namespace"`      // Namespace for Cell B logs
}

type GoldfishMetrics added in v3.6.0

type GoldfishMetrics struct {
	// contains filtered or unexported fields
}

GoldfishMetrics contains all goldfish-related metrics

func NewGoldfishMetrics added in v3.6.0

func NewGoldfishMetrics(reg prometheus.Registerer) *GoldfishMetrics

NewGoldfishMetrics creates and registers goldfish metrics

func (*GoldfishMetrics) IncrementDBConnectionErrors added in v3.6.0

func (m *GoldfishMetrics) IncrementDBConnectionErrors(errorType string)

IncrementDBConnectionErrors increments connection errors

func (*GoldfishMetrics) IncrementErrors added in v3.6.0

func (m *GoldfishMetrics) IncrementErrors(errorType string)

IncrementErrors increments the error counter

func (*GoldfishMetrics) IncrementRequests added in v3.6.0

func (m *GoldfishMetrics) IncrementRequests(status string)

IncrementRequests increments the request counter

func (*GoldfishMetrics) RecordDBConnectionWait added in v3.6.0

func (m *GoldfishMetrics) RecordDBConnectionWait(pool string, duration float64)

RecordDBConnectionWait records connection wait time

func (*GoldfishMetrics) RecordQueryDuration added in v3.6.0

func (m *GoldfishMetrics) RecordQueryDuration(queryType, status string, duration float64)

RecordQueryDuration records the duration of a database query

func (*GoldfishMetrics) RecordQueryRows added in v3.6.0

func (m *GoldfishMetrics) RecordQueryRows(queryType string, rows float64)

RecordQueryRows records the number of rows returned

func (*GoldfishMetrics) SetDBConnections added in v3.6.0

func (m *GoldfishMetrics) SetDBConnections(state string, count float64)

SetDBConnections sets the current connection count

type Member

type Member struct {
	Addr     string         `json:"addr"`
	Target   string         `json:"target"`
	Services []ServiceState `json:"services"`
	Build    BuildInfo      `json:"build"`
	Error    error          `json:"error,omitempty"`
	Ready    ReadyResponse  `json:"ready,omitempty"`
	// contains filtered or unexported fields
}

Member represents a node in the cluster with its current state and capabilities.

type NodeDetails

type NodeDetails struct {
	Member
	Config          string                 `json:"config"`
	ClusterID       string                 `json:"clusterID"`
	ClusterSeededAt int64                  `json:"clusterSeededAt"`
	OS              string                 `json:"os"`
	Arch            string                 `json:"arch"`
	Edition         string                 `json:"edition"`
	Metrics         map[string]interface{} `json:"metrics"`
}

NodeDetails contains the details of a node in the cluster. It adds on top of Member the config, build, clusterID, clusterSeededAt, os, arch, edition and registered analytics metrics.

type ReadyResponse

type ReadyResponse struct {
	IsReady bool   `json:"isReady"`
	Message string `json:"message"`
}

type SampledQuery added in v3.6.0

type SampledQuery struct {
	// Core query identification
	CorrelationID string `json:"correlationId" db:"correlation_id"`
	TenantID      string `json:"tenantId" db:"tenant_id"`
	User          string `json:"user" db:"user"`
	Query         string `json:"query" db:"query"`
	QueryType     string `json:"queryType" db:"query_type"`

	// Time range fields - stored as RFC3339 strings for API compatibility
	StartTime    string `json:"startTime" db:"start_time"`       // RFC3339 formatted
	EndTime      string `json:"endTime" db:"end_time"`           // RFC3339 formatted
	StepDuration *int64 `json:"stepDuration" db:"step_duration"` // Step in milliseconds, nullable

	// Performance statistics - flattened from QueryStats for API simplicity
	// All are nullable as some queries might not have complete stats
	CellAExecTimeMs      *int64 `json:"cellAExecTimeMs" db:"cell_a_exec_time_ms"`
	CellBExecTimeMs      *int64 `json:"cellBExecTimeMs" db:"cell_b_exec_time_ms"`
	CellAQueueTimeMs     *int64 `json:"cellAQueueTimeMs" db:"cell_a_queue_time_ms"`
	CellBQueueTimeMs     *int64 `json:"cellBQueueTimeMs" db:"cell_b_queue_time_ms"`
	CellABytesProcessed  *int64 `json:"cellABytesProcessed" db:"cell_a_bytes_processed"`
	CellBBytesProcessed  *int64 `json:"cellBBytesProcessed" db:"cell_b_bytes_processed"`
	CellALinesProcessed  *int64 `json:"cellALinesProcessed" db:"cell_a_lines_processed"`
	CellBLinesProcessed  *int64 `json:"cellBLinesProcessed" db:"cell_b_lines_processed"`
	CellABytesPerSecond  *int64 `json:"cellABytesPerSecond" db:"cell_a_bytes_per_second"`
	CellBBytesPerSecond  *int64 `json:"cellBBytesPerSecond" db:"cell_b_bytes_per_second"`
	CellALinesPerSecond  *int64 `json:"cellALinesPerSecond" db:"cell_a_lines_per_second"`
	CellBLinesPerSecond  *int64 `json:"cellBLinesPerSecond" db:"cell_b_lines_per_second"`
	CellAEntriesReturned *int64 `json:"cellAEntriesReturned" db:"cell_a_entries_returned"`
	CellBEntriesReturned *int64 `json:"cellBEntriesReturned" db:"cell_b_entries_returned"`
	CellASplits          *int64 `json:"cellASplits" db:"cell_a_splits"`
	CellBSplits          *int64 `json:"cellBSplits" db:"cell_b_splits"`
	CellAShards          *int64 `json:"cellAShards" db:"cell_a_shards"`
	CellBShards          *int64 `json:"cellBShards" db:"cell_b_shards"`

	// Response metadata - nullable for error cases
	CellAResponseHash *string `json:"cellAResponseHash" db:"cell_a_response_hash"`
	CellBResponseHash *string `json:"cellBResponseHash" db:"cell_b_response_hash"`
	CellAResponseSize *int64  `json:"cellAResponseSize" db:"cell_a_response_size"`
	CellBResponseSize *int64  `json:"cellBResponseSize" db:"cell_b_response_size"`
	CellAStatusCode   *int    `json:"cellAStatusCode" db:"cell_a_status_code"`
	CellBStatusCode   *int    `json:"cellBStatusCode" db:"cell_b_status_code"`

	// Trace IDs - nullable as not all requests have traces
	CellATraceID *string `json:"cellATraceID" db:"cell_a_trace_id"`
	CellBTraceID *string `json:"cellBTraceID" db:"cell_b_trace_id"`
	CellASpanID  *string `json:"cellASpanID" db:"cell_a_span_id"`
	CellBSpanID  *string `json:"cellBSpanID" db:"cell_b_span_id"`

	// Query engine version tracking
	CellAUsedNewEngine bool `json:"cellAUsedNewEngine" db:"cell_a_used_new_engine"`
	CellBUsedNewEngine bool `json:"cellBUsedNewEngine" db:"cell_b_used_new_engine"`

	// Timestamps - time.Time for database scanning, formatted in JSON marshaling
	SampledAt time.Time `json:"sampledAt" db:"sampled_at"`
	CreatedAt time.Time `json:"createdAt" db:"created_at"`

	// Comparison outcome - computed by backend logic
	ComparisonStatus string `json:"comparisonStatus" db:"comparison_status"`

	// UI-only fields - generated based on configuration, not stored in database
	CellATraceLink *string `json:"cellATraceLink,omitempty"`
	CellBTraceLink *string `json:"cellBTraceLink,omitempty"`
	CellALogsLink  *string `json:"cellALogsLink,omitempty"`
	CellBLogsLink  *string `json:"cellBLogsLink,omitempty"`
}

SampledQuery represents a sampled query from the database for API responses. This is the UI/API representation of goldfish.QuerySample with several important differences:

1. Time formatting: All time fields use RFC3339 strings instead of time.Time

  • The frontend expects RFC3339 formatted strings for display
  • Database columns store timestamps that are scanned into time.Time then formatted

2. Nullable fields: Uses pointers (*int64, *string) for nullable database columns

  • The database schema allows NULLs for metrics that might not be available
  • Go's zero values would be ambiguous (is 0 a real value or NULL?)

3. Flattened structure: QueryStats fields are flattened into individual columns

  • Makes the API response simpler for frontend consumption
  • Matches the database schema which stores stats as individual columns

4. Database tags: Includes `db:` tags for direct sqlx scanning from queries

  • The storage layer returns goldfish.QuerySample for internal use
  • The UI layer queries the database directly for performance

5. UI-specific fields: Includes trace/logs links generated from configuration

  • These are computed based on Grafana configuration, not stored

type Service

type Service struct {
	services.Service
	// contains filtered or unexported fields
}

func NewService

func NewService(cfg Config, router *mux.Router, ring *ring.Ring, localAddr string, logger log.Logger, reg prometheus.Registerer) (*Service, error)

func (*Service) GenerateLogsExploreURL added in v3.6.0

func (s *Service) GenerateLogsExploreURL(traceID, namespace string, sampledAt time.Time) string

GenerateLogsExploreURL generates a Grafana Explore URL for logs related to a trace ID

func (*Service) GenerateTraceExploreURL added in v3.6.0

func (s *Service) GenerateTraceExploreURL(traceID, spanID string, sampledAt time.Time) string

GenerateTraceExploreURL generates a Grafana Explore URL for a given trace ID

func (*Service) GetSampledQueries added in v3.6.0

func (s *Service) GetSampledQueries(page, pageSize int, filter goldfish.QueryFilter) (*GoldfishAPIResponse, error)

GetSampledQueries retrieves sampled queries from the database with pagination and outcome filtering

func (*Service) GetSampledQueriesWithContext added in v3.6.0

func (s *Service) GetSampledQueriesWithContext(ctx context.Context, page, pageSize int, filter goldfish.QueryFilter) (*GoldfishAPIResponse, error)

GetSampledQueriesWithContext retrieves sampled queries with trace context

func (*Service) RegisterHandler

func (s *Service) RegisterHandler()

RegisterHandler registers all UI API routes with the provided router.

type ServiceState

type ServiceState struct {
	Service string `json:"service"`
	Status  string `json:"status"`
}

ServiceState represents the current state of a service running on a member.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL