Grafana Loki Workshop - Quick Reference Guide
Version: 1.0 Date: 2026-04-15 Source: https://github.com/grafana/loki-workshop Documentation: https://grafana.github.io/loki-workshop/
Table of Contents
- Workshop Overview
- Prerequisites and Environment
- Lab 1: Log Exploration (Logs Drilldown)
- Lab 2: LogQL Queries and Correlation
- Lab 3: Metric Queries
- Lab 4: Dashboard Building
- LogQL Reference
- Common Commands and Patterns
- Docker Compose Setup (Local Run)
Workshop Overview
Learning Objectives
The workshop covers three main areas:
- Navigating and exploring logs in Grafana
- Using the Logs Drilldown app
- Filtering, searching, and drilling down
-
Instant metrics from logs
-
Writing and running Loki queries
- LogQL syntax
- Label and line filters
- Query-time JSON parsing
-
Correlation between metrics, logs, and traces
-
Visualizing Loki query results on dashboards
- Metric extraction from logs
- Unwrapped range aggregations
- Building dashboard panels
- Line format transformations
Lab Structure
- Lab 1: Exploring Logs (Logs Drilldown App)
- Lab 2: Query and Correlate Logs (LogQL + Prometheus/Tempo)
- Lab 3: Metric Queries (count_over_time, unwrap, aggregations)
- Lab 4: Dashboard Building (95th percentile, Geomap, Log panels)
Prerequisites and Environment
Grafana Requirements
- Version: Grafana 11 or higher
- Access: Administrator permissions
- Required plugin: Logs Drilldown (must be installed)
Plugin installation (self-hosted instance):
Required Permissions
- Add data sources
- Create dashboards
- Use Explore
Data Sources
The workshop requires three data sources (connection details provided by presenter):
- Loki - Log aggregation and querying
- Prometheus - Metrics correlation
- Tempo - Trace correlation
Grafana Cloud Alternative
- Free tier: https://grafana.com/auth/sign-up/create-user
- Advantage: Managed Grafana instance with Logs Drilldown plugin pre-installed
Lab 1: Log Exploration (Logs Drilldown)
1.1. Viewing Service Logs
Opening Logs Drilldown App:
Service list:
- All services listed by service_name label
- Scroll and click "Show logs" button
Log View components: 1. Log volume chart - Log volume over time 2. Log lines list - Service log entries 3. Time range picker - Top right corner
1.2. Log Line Details
Expanding a log line: - Click on a log line → details appear
Field types:
| Icon | Type | Description |
|---|---|---|
| I | Indexed labels | Labels in Loki's index |
| P | Parsed labels | Query-time parsed fields |
| S | Structured metadata | Un-indexed key-value pairs |
Field actions: - Magnifying glass (+) - Add field to filter (include) - Magnifying glass (-) - Exclude field from filter
1.3. Searching and Filtering
String search:
- Only shows lines containing the "favicon" stringAdding field filter:
1. Expand a log line
2. Next to field: click magnifying glass (+) icon
3. Example: status_code = 404
Modifying filters: - Click on filter chips at the top - Select new value from dropdown
Example combination:
→ Only lines containing "favicon" AND with 404 status_code1.4. Metrics from Logs (Labels Tab)
Opening Labels tab: - From tab row above log volume chart: Labels
What it shows: - Breakdown by Loki labels - Chart for each label value
Drilldown:
1. Select a panel (e.g., http_method)
2. Select button → Breakdown by this label
3. Include button next to a value → Filter to only this value
Example workflow:
→ Only GET requests visible1.5. Returning to Logs Tab
- Click Logs tab → Log list visible again
- Applied filters persist
Lab 2: LogQL Queries and Correlation
2.1. Switching to Explore Mode
Logs Drilldown → Explore:
What we see: - The LogQL query generated by Logs Drilldown - Log volume chart - Query editor
2.2. LogQL Syntax Basics
Basic LogQL query structure:
Components:
| Part | Description |
|---|---|
{label="value"} |
Stream selector - which log streams to select |
\|= |
Line filter - contains string |
\|~ |
Regex filter - regex pattern match |
\| json |
Parser - JSON parsing |
\| logfmt |
Parser - logfmt parsing |
Example query:
Explanation:
1. {service_name=web_app_3} - Only web_app_3 service logs
2. | json - Parse JSON fields
3. |~(?i)favicon.ico`` - Regex filter: favicon.ico (case-insensitive)
2.3. Table View
Activating Table toggle:
Fields panel: - Left side panel: Detected labels and fields - Checkboxes: Add/remove columns
Example columns:
- status_code
- http_method
- Line (original log line)
Export functions:
2.4. Prometheus Metrics Correlation
Writing Prometheus query:
Using Metrics Browser:
Adding label filter:
Correlation setup:
- Split view: Split button (next to time picker)
- Second panel: Switch to LokiCorrelation data source
- Time sync: Chain button (next to time picker) - orange when active
- Zoom on Prometheus chart: Click + drag to select time period → Loki panel automatically follows time range
Error searching:
→ Detect out of memory error2.5. Tempo Traces Correlation
Finding Trace ID:
1. Zoom out on time range (zoom out button)
2. Search for log line with tempo_trace_id field
Opening trace: 1. Expand log line 2. Next to Trace ID field: Tempo or View traces button (blue) 3. Grafana Trace View opens
What we see: - Full request/response flow as spans - Bottleneck identification - Latency breakdown
Lab 3: Metric Queries
3.1. Log Range Aggregation (count_over_time)
Goal: Calculate metric based on number of log lines
Example: Googlebot request rate by status code
sum by(status) (
count_over_time(
{filename="/var/log/nginx/json_access.log"}
|= `Googlebot`
| json
[5m]
)
)
Explanation:
1. {filename="..."} - Stream selector
2. |= 'Googlebot' - Line filter
3. | json - JSON parser
4. count_over_time(...[5m]) - Count log lines in 5-minute windows
5. sum by(status) - Aggregate by status label
Query-time parsing:
- Loki doesn't pre-index JSON content
- | json parser can extract any field during query
- These fields can be used for aggregation
3.2. Unwrapped Range Aggregation
Goal: Calculate metric based on value in log line (e.g., bytes_sent, request_time)
Example: Average bytes sent to Googlebot
avg_over_time(
{filename="/var/log/nginx/json_access.log"}
|= "Googlebot"
| json
| unwrap bytes_sent
[5m]
) by (host)
Explanation:
1. | json - Parse JSON
2. | unwrap bytes_sent - Use the bytes_sent field value
3. avg_over_time(...[5m]) - Average calculation in 5-minute windows
4. by (host) - Breakdown by host
Example: Maximum bytes sent
max_over_time(
{filename="/var/log/nginx/json_access.log"}
|= "Googlebot"
| json
| unwrap bytes_sent
[5m]
) by (host)
Unwrap functions:
| Function | Description |
|---|---|
avg_over_time() |
Average value in time window |
max_over_time() |
Maximum value |
min_over_time() |
Minimum value |
sum_over_time() |
Sum |
quantile_over_time(0.95, ...) |
95th percentile |
3.3. Combining Multiple Queries
Add query button:
Example: All requests vs Googlebot requests
# Query A: All requests
sum by (request_method) (
count_over_time(
{filename="/var/log/nginx/json_access.log"}
| json
[5m]
)
)
# Query B: Googlebot requests by status
sum by(status) (
count_over_time(
{filename="/var/log/nginx/json_access.log"}
|= `Googlebot`
| json
[5m]
)
)
→ Both queries appear on the same chart for comparison
Lab 4: Dashboard Building
4.1. Creating Dashboard
New dashboard:
Save dashboard:
Set time range:
4.2. Panel 1: 95th Percentile Request Time
Add visualization:
Query 1: 95th percentile
quantile_over_time(0.95,
{filename="/var/log/nginx/json_access.log"}
| json
| upstream_cache_status="MISS"
| unwrap request_time
| __error__=""
[5m]
) by (host)
Query 2: Max request time
max_over_time(
{filename="/var/log/nginx/json_access.log"}
| json
| upstream_cache_status="MISS"
| unwrap request_time
| __error__=""
[1m]
) by (host)
Legend settings:
Panel title:
What it shows: - 95% of requests faster than this value - Maximum request time every 1 minute - Only cache MISS requests (cache hits filtered out)
4.3. Panel 2: Googlebot Request Percentage (Stat Panel)
Add visualization:
Query: Percentage calculation
sum(rate(({filename="/var/log/nginx/json_access.log"} |= "Googlebot")[10m]))
/
(sum(rate(({filename="/var/log/nginx/json_access.log"} |= "Mozilla")[10m])) / 100)
Explanation:
- rate(...[10m]) - Request rate per second in 10-minute windows
- Googlebot requests / (Mozilla requests / 100) = percentage
Visualization settings:
Panel type: Stat (top dropdown)
Value options → Calculation: Last
Standard Options → Unit: Misc → Percent (0-100)
Panel Title: "Current % of request by Google"
What it shows: - Large, bold number (current value) - Googlebot traffic as percentage of all (Mozilla) traffic
4.4. Panel 3: Geomap (Country Code)
Add visualization:
Query: Count by country
sum by (geoip_country_code) (
count_over_time(
{filename="/var/log/nginx/json_access.log"}
| json
| __error__=""
[1m]
)
)
Legend:
Transformation:
Visualization:
Map layers:
Layer 1 (bottom): ArcGIS MapServer
Layer 2 (top): Markers
- Location Mode: Lookup
- Lookup field: Field
- Gazetteer: Countries
- Styles Size: Total (Min: 10, Max: 40)
- Color: Fixed Color → Red
- Fill opacity: 0.8
Panel title:
4.5. Panel 4: Reformatted Logs (line_format)
Add visualization:
Query: Line format transformation
{filename="/var/log/nginx/json_access.log"}
| json
| line_format " request for {{.request_uri}} with HTTP status: {{.status}} "
Explanation:
1. | json - Parse JSON fields
2. | line_format "..." - Rewrite log line
3. {{.field_name}} - Reference JSON field
What it shows: - Custom formatted log lines - Decorated with emojis - Only essential information (request_uri, status)
Panel title:
4.6. Dashboard Import (Demo)
Import pre-built dashboard:
Set dashboard variables:
Example panels: - % of 5xx requests - Top requested pages - Top user agents - Top IP addresses - Logs panel
View query:
LogQL Reference
Stream Selectors
Label matching:
{label="exact_value"} # Exact match
{label=~"regex_pattern"} # Regex match
{label!="not_this_value"} # Not equal
{label!~"not_this_regex"} # Regex not match
Multiple labels (AND):
Line Filters
|= "string" # Contains string
|~ "regex" # Regex match
!= "string" # Does NOT contain
!~ "regex" # Regex NOT match
Examples:
{service="web"} |= "error" # Contains "error"
{service="web"} |~ "(?i)error|warn" # Regex: error OR warn (case-insensitive)
{service="web"} != "debug" # Does NOT contain "debug"
Parsers
| json # Parse JSON
| logfmt # Parse logfmt (key=value)
| pattern `<pattern>` # Custom pattern
| regexp `(?P<name>regex)` # Named capture groups
JSON parser example:
Label Filters (Parsed)
Line Format
Rewrite log line:
Example:
Label Format
Create/modify labels:
Example:
Metrics Functions
Count-based (log lines)
count_over_time({service="web"}[5m]) # Count log lines
rate({service="web"}[5m]) # Logs per second
bytes_over_time({service="web"}[5m]) # Total bytes
Unwrap-based (values in logs)
| unwrap field_name # Extract numeric field
avg_over_time(... | unwrap latency [5m]) # Average value
sum_over_time(... | unwrap bytes [5m]) # Sum of values
max_over_time(... | unwrap duration [5m]) # Maximum value
min_over_time(... | unwrap size [5m]) # Minimum value
quantile_over_time(0.95, ... | unwrap latency [5m]) # 95th percentile
Aggregation Operators
sum by (label) (...) # Sum grouped by label
avg by (label) (...) # Average grouped by label
max by (label) (...) # Maximum grouped by label
min by (label) (...) # Minimum grouped by label
count by (label) (...) # Count grouped by label
Without labels (all streams combined):
Binary Operations
Math on metrics:
Example: Percentage
Common Commands and Patterns
1. Error Rate Calculation
Error logs per second:
Error percentage:
2. HTTP Status Code Distribution
Count by status code:
Rate of 5xx errors:
3. Top N Queries
Top 10 requested paths:
Bottom 5 slowest endpoints:
4. Response Time Percentiles
P50, P95, P99:
# P50 (median)
quantile_over_time(0.50, {service="web"} | json | unwrap latency [5m]) by (endpoint)
# P95
quantile_over_time(0.95, {service="web"} | json | unwrap latency [5m]) by (endpoint)
# P99
quantile_over_time(0.99, {service="web"} | json | unwrap latency [5m]) by (endpoint)
5. Correlation Patterns
Prometheus → Loki:
Loki → Tempo:
→ Click trace_id field → View traces button6. Alert Queries (for Alerting Rules)
High error rate:
P95 latency too high:
7. Filtering Out Noise
Exclude health checks:
Exclude specific IPs:
8. Multi-line Log Parsing
Pattern extraction:
Regex with named groups:
{service="app"}
| regexp `(?P<timestamp>\d{4}-\d{2}-\d{2}) (?P<level>\w+) (?P<msg>.+)`
| level="FATAL"
Docker Compose Setup (Local Run)
compose.yaml
Clone repository:
Environment variables (.env file):
Required variables:
LOKI_URL=https://your-loki-instance.com
LOKI_USERNAME=your-username
LOKI_PASSWORD=your-password
TEMPO_URL=https://your-tempo-instance.com
TEMPO_USERNAME=your-username
TEMPO_PASSWORD=your-password
Docker Compose Service
compose.yaml content:
version: '3'
services:
grafana:
image: docker.io/grafana/grafana:latest
ports:
- "3007:3000"
environment:
GF_PLUGINS_PREINSTALL: grafana-lokiexplore-app
GF_AUTH_ANONYMOUS_ENABLED: true
GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
GF_USERS_DEFAULT_THEME: light
GF_LOG_FILTERS: migrator:warn
LOKI_URL: ${LOKI_URL}
LOKI_USERNAME: ${LOKI_USERNAME}
LOKI_PASSWORD: ${LOKI_PASSWORD}
TEMPO_URL: ${TEMPO_URL}
TEMPO_USERNAME: ${TEMPO_USERNAME}
TEMPO_PASSWORD: ${TEMPO_PASSWORD}
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:z
Start:
Access Grafana:
Stop:
Taskfile Usage (Task Runner)
Taskfile.yaml tasks:
Start development docs:
→ Local preview of workshop docs: http://localhost:3000Useful Links and Resources
Official Documentation
- Loki Documentation: https://grafana.com/docs/loki/latest/
- LogQL Reference: https://grafana.com/docs/loki/latest/query/
- Loki Workshop GitHub: https://github.com/grafana/loki-workshop
- Workshop Docs: https://grafana.github.io/loki-workshop/
Grafana Plugins
- Logs Drilldown: https://grafana.com/grafana/plugins/grafana-lokiexplore-app/
- Grafana Plugin Catalog: https://grafana.com/grafana/plugins/
Example Dashboards
- NGINX Analytics (ID: 24306): Official workshop demo dashboard
- Grafana Dashboards: https://grafana.com/grafana/dashboards/?search=loki
Learning Resources
- Grafana Labs YouTube: Loki tutorials, webinars
- Grafana Community: https://community.grafana.com/
- Loki Blog Posts: https://grafana.com/blog/tags/loki/
Common Issues and Solutions
1. Logs Drilldown not loading logs
Problem: Service list empty or logs not showing
Solution: - Check the correct data source is selected (top right corner) - Ensure LokiCorrelation data source is set - Verify time range (long enough time window?)
2. Parsed fields not appearing
Problem: No fields after JSON parser
Solution:
# Wrong: no parser
{service="web"} |= "error"
# Correct: JSON parser added
{service="web"} |= "error" | json
3. Metric query returns "no data"
Problem: count_over_time() or unwrap query returns no data
Solution: - Verify label selector finds streams - Increase time range - Check unwrap field type (must be numeric)
# Wrong: unwrap string field
| unwrap status_code # If status_code is string "404"
# Correct: unwrap numeric field
| unwrap bytes_sent # Numeric field
4. Dashboard panel shows "N/A"
Problem: Stat or Gauge panel shows "N/A" instead of number
Solution: - Value options → Calculation: "Last" (or "Last (not null)") - Query time range must match dashboard time range
5. Geomap not displaying countries
Problem: Markers not appearing on map
Solution: - Transformation: Reduce transformation in "Series to rows" mode - Lookup field: Set to "Field" - Gazetteer: Select "Countries" - Label name must match country code format (ISO 2-letter: "US", "GB", etc.)
Next Steps
Reinforcing Basics
- Practice Logs Drilldown app (search, filter, labels)
- Write basic LogQL queries (stream selector, line filter, parser)
- Metric extraction from logs (count_over_time, unwrap)
- Build dashboard panels
Advanced Topics
- Loki Recording Rules: Pre-calculated metrics
- Alerting Rules: Alert notifications based on LogQL queries
- Loki Ruler: Alert rule management
- Multi-tenancy: Organization isolation in Loki
- Label Cardinality: Best practices for label usage
Production Deployment
- Loki Architecture: Distributor, Ingester, Querier components
- Storage Backend: S3, GCS, Azure Blob
- Retention Policies: Data lifecycle management
- Performance Tuning: Query optimization, caching
Integration
- Promtail: Log shipping to Loki
- Grafana Agent: Unified observability agent
- OpenTelemetry Collector: OTEL logs to Loki
- FluentBit/Fluentd: Alternative log collectors
Summary
This quick reference covers the complete Grafana Loki Workshop material with practical examples and commands.
During the workshop, you learned: - Use Logs Drilldown app for fast log exploration - Write LogQL queries (stream selectors, filters, parsers) - Calculate metrics from logs (count, unwrap, aggregations) - Build dashboards with Loki queries - Use correlation between Prometheus and Tempo
Next step: - Practice queries on your own Loki instance - Build production-ready dashboards - Explore advanced LogQL features (recording rules, alerts)
Happy learning!