Introduction


Centralized logging transforms noisy application output into a searchable, actionable resource. The three leading approaches--ELK Stack (Elasticsearch, Logstash, Kibana), Grafana Loki, and Splunk--take fundamentally different approaches to log ingestion, indexing, and querying. Choosing the right one affects both your daily operations and your monthly infrastructure bill.


Architecture Comparison


ELK Stack


Elasticsearch indexes every field in every log line, enabling rich full-text search at the cost of higher storage consumption:



# Filebeat configuration for shipping logs

filebeat.inputs:

  - type: container

    paths:

      - /var/lib/docker/containers/*/*.log

    json.message_key: log

    json.overwrite_keys: true

    json.add_error_key: true



processors:

  - add_docker_metadata:

      host: "unix:///var/run/docker.sock"

  - dissect:

      tokenizer: "%{timestamp} %{level} %{logger} %{message}"

      target_prefix: "parsed"



output.elasticsearch:

  hosts: ["elasticsearch:9200"]

  index: "filebeat-%{[agent.version]}-%{+yyyy.MM.dd}"

  pipeline: "parse-logs"



# Elasticsearch index lifecycle policy

PUT _ilm/policy/logs-30day

{

  "policy": {

    "phases": {

      "hot": {

        "min_age": "0ms",

        "actions": {

          "rollover": { "max_size": "50GB", "max_age": "1d" },

          "set_priority": { "priority": 100 }

        }

      },

      "warm": {

        "min_age": "3d",

        "actions": {

          "shrink": { "number_of_shards": 1 },

          "forcemerge": { "max_num_segments": 1 }

        }

      },

      "delete": {

        "min_age": "30d",

        "actions": { "delete": {} }

      }

    }

  }

}


Grafana Loki


Loki indexes only metadata labels, leaving log content unindexed for dramatically lower storage costs:



# Loki configuration

auth_enabled: false



server:

  http_listen_port: 3100



common:

  replication_factor: 1

  ring:

    kvstore:

      store: inmemory



schema_config:

  configs:

    - from: 2024-01-01

      store: tsdb

      object_store: s3

      schema: v13

      index:

        prefix: index_

        period: 24h



storage_config:

  tsdb_shipper:

    active_index_directory: /loki/tsdb-shipper-active

    cache_location: /loki/tsdb-shipper-cache

    shared_store: s3

  aws:

    s3: s3://us-east-1/logs-bucket

    s3forcepathstyle: false



limits_config:

  retention_period: 30d

  max_query_series: 10000

  ingestion_rate_mb: 10

  ingestion_burst_size_mb: 20



compactor:

  working_directory: /loki/compactor

  shared_store: s3

  retention_enabled: true


Splunk


Splunk uses a proprietary indexer architecture with heavy indexing at ingest:



# inputs.conf - Splunk forwarder configuration

[monitor:///var/log/app/*.log]

disabled = false

index = production

sourcetype = applog

crcSalt = <SOURCE>



# props.conf - Field extraction

[applog]

SHOULD_LINEMERGE = true

LINE_BREAKER = ([\r\n]+)

TRUNCATE = 20000

KV_MODE = json

REPORT-appfields = app-timestamp-extract, app-level-extract



# transforms.conf

[app-timestamp-extract]

REGEX = (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})

FORMAT = date::$1



[app-level-extract]

REGEX = "level":"(\w+)"

FORMAT = level::$1


Structured Logging


No matter which platform you choose, structured logging at the application level is essential:



package main



import (

    "go.uber.org/zap"

)



func main() {

    logger, _ := zap.NewProduction(

        zap.WithCaller(true),

        zap.AddStacktrace(zap.ErrorLevel),

    )

    defer logger.Sync()



    orderID := "ord-12345"

    amount := 99.50



    // Structured log with context

    logger.Info("processing payment",

        zap.String("order_id", orderID),

        zap.Float64("amount", amount),

        zap.String("currency", "USD"),

        zap.String("payment_method", "credit_card"),

        zap.Duration("processing_time", 250*time.Millisecond),

    )

}


The same structured log renders differently across platforms:



{

  "level": "info",

  "ts": "2026-05-12T10:30:00Z",

  "caller": "payment.go:42",

  "msg": "processing payment",

  "order_id": "ord-12345",

  "amount": 99.50,

  "currency": "USD",

  "payment_method": "credit_card",

  "processing_time": "250ms"

}


Query Capabilities


ELK (Kibana Query Language)



service:payment AND level:ERROR

AND NOT message:"timeout retry"

AND @timestamp >= "now-1h"


Loki (LogQL)



{service="payment", level="error"}

|= "charge_failed"

| json

| duration > 5s

| unwrap latency_ms [5m]

| rate per second


Splunk (SPL)



index=production sourcetype=applog

service=payment level=ERROR

| rex field=message "order_id=(?<order_id>\S+)"

| stats count by order_id

| sort - count

| head 10


Indexing Strategies


| Strategy | ELK | Loki | Splunk |

|---|---|---|---|

| Index approach | Inverted index on all fields | Label-only index | Proprietary inverted index |

| Storage ratio | 1:1.5 (raw to indexed) | 1:0.3 (minimal overhead) | 1:2+ (heavily optimized) |

| Cardinality limits | 20k fields/document | Label value cardinality | 10k source types |

| Full-text search | Excellent | Limited (filter + grep) | Excellent |


Retention Policies



# Loki: retention via compactor

compactor:

  retention_enabled: true

  retention_rules:

    - type: series

      selector:

        match: '{service="payment"}'

      priority: 10

      period: 90d  # Payment logs retained longer

    - selector:

        match: '{env="staging"}'

      priority: 1

      period: 7d   # Staging logs retained shorter


Cost Comparison


| Factor | ELK (self-hosted) | ELK (Elastic Cloud) | Loki + S3 | Splunk |

|---|---|---|---|---|

| Storage cost | $0.10/GB/month (SSD) | $0.30/GB/month | $0.023/GB/month (S3) | $2/GB/month (ingested) |

| Compute | 3-5 nodes minimum | $500+/month base | 2 nodes minimum | $2,000+/GB/day |

| Free tier | No | 500MB/month | Community (unlimited) | 500MB/day |


Loki offers the lowest storage cost by far due to its S3-based object storage and label-only indexing. ELK provides the best query flexibility at moderate cost. Splunk delivers enterprise-grade reliability but at a premium that only makes sense for compliance-heavy industries.


Decision Matrix


  • **ELK Stack**: Best for teams needing full-text search across all fields, complex aggregations, and existing Elasticsearch expertise.
  • **Grafana Loki**: Best for cost-conscious teams already using Grafana, who can tolerate grep-style log searching.
  • **Splunk**: Best for enterprises requiring compliance auditing, RBAC, and have budget for premium support.

  • For most engineering teams, Loki paired with Grafana offers the best balance of cost and capability. Move to ELK when you need indexed structured search on arbitrary fields. Reserve Splunk for regulated environments where audit trails and access controls justify the expense.