Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
446 lines
16 KiB
Plaintext
446 lines
16 KiB
Plaintext
---
|
||
title: "Deploy Grafana Loki for centralized logging"
|
||
---
|
||
<PlanAvailability slug="entry-ent" />
|
||
|
||
This guide extends your existing [Prometheus and Grafana performance monitoring deployment](/administration-guide/scale/deploy-prometheus-grafana-for-performance-monitoring) by adding [Grafana Loki](https://grafana.com/oss/loki/) for centralized log aggregation. While Prometheus collects **metrics** (CPU, request latency, goroutine counts), Loki collects **logs** — giving your operations team the ability to search, filter, and correlate Mattermost application logs across all servers from a single Grafana interface.
|
||
|
||
With Loki in place you can:
|
||
|
||
- Search Mattermost logs across all application servers from Grafana.
|
||
- Filter by log level, HTTP status code, or free-text search.
|
||
- Identify the top recurring errors and warnings.
|
||
- Correlate log events with Prometheus metric spikes on the same timeline.
|
||
- Optionally collect PostgreSQL database logs alongside application logs.
|
||
|
||
<Tip>
|
||
|
||
This guide assumes you have already deployed Prometheus and Grafana by following the [performance monitoring guide](/administration-guide/scale/deploy-prometheus-grafana-for-performance-monitoring). Loki and the OpenTelemetry Collector will be added to that existing infrastructure.
|
||
|
||
</Tip>
|
||
|
||
## Architecture overview
|
||
|
||
The deployment adds two components to your monitoring stack:
|
||
|
||
- **Loki** — the log aggregation engine, installed on your existing Grafana/Prometheus monitoring server.
|
||
- **OpenTelemetry Collector** — a versatile, industry-standard agent installed on each Mattermost application server to ship logs.
|
||
|
||
``` text
|
||
┌──────────────────────┐ ┌──────────────────────┐
|
||
│ Mattermost App 01 │ │ Mattermost App 02 │
|
||
│ │ │ │
|
||
│ /opt/mattermost/ │ │ /opt/mattermost/ │
|
||
│ logs/mattermost.log│ │ logs/mattermost.log│
|
||
│ │ │ │ │ │
|
||
│ [ OTel Col ] │ │ [ OTel Col ] │
|
||
└─────────┬────────────┘ └─────────┬────────────┘
|
||
│ push (:3100/otlp) │
|
||
▼ ▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Monitoring Server │
|
||
│ │
|
||
│ [ Prometheus :9090 ] [ Loki :3100 ] │
|
||
│ │ │ │
|
||
│ └──────┬──────────────┘ │
|
||
│ ▼ │
|
||
│ [ Grafana :3000 ] │
|
||
│ Metrics + Logs │
|
||
└─────────────────────────────────────────────────────┘
|
||
|
||
Optional: Add the OpenTelemetry Collector on your PostgreSQL server to also
|
||
ship database logs to Loki.
|
||
```
|
||
|
||
## Prerequisites
|
||
|
||
Before starting, confirm the following:
|
||
|
||
- Prometheus and Grafana are deployed and collecting Mattermost metrics per the [performance monitoring guide](/administration-guide/scale/deploy-prometheus-grafana-for-performance-monitoring).
|
||
- You have **SSH access** with sudo privileges to the monitoring server and each Mattermost application server.
|
||
- Mattermost is installed at `/opt/mattermost` (the default location). If your installation path differs, substitute your path wherever `/opt/mattermost` appears.
|
||
- **Network connectivity**: Each Mattermost server can reach the monitoring server on **TCP port 3100** (Loki's HTTP API).
|
||
|
||
## Step 1: Verify Mattermost JSON logging
|
||
|
||
Mattermost must be writing JSON-formatted file logs for Loki queries to work. This is the default on all plans — verify it on each application server:
|
||
|
||
``` bash
|
||
tail -1 /opt/mattermost/logs/mattermost.log
|
||
```
|
||
|
||
A JSON-formatted line looks like:
|
||
|
||
``` json
|
||
{"timestamp":"2025-01-15T14:32:01.123Z","level":"info","msg":"Server is listening on [::]:8065","caller":"app/server.go:482"}
|
||
```
|
||
|
||
If you see plain-text output instead, go to **System Console \> Environment \> Logging** and set **Output file logs as JSON** to `true`, then restart Mattermost:
|
||
|
||
``` bash
|
||
sudo systemctl restart mattermost
|
||
```
|
||
|
||
## Step 2: Install Loki on the monitoring server
|
||
|
||
Install Loki on the same server that runs Grafana and Prometheus. All commands in this section are run **on the monitoring server**.
|
||
|
||
<Important>
|
||
|
||
The commands below use Loki version **3.4.2**. Check the [Loki releases page](https://github.com/grafana/loki/releases) for the latest version and substitute the version number as needed.
|
||
|
||
</Important>
|
||
|
||
1. Create the Loki user, directories, and download the binary:
|
||
|
||
``` bash
|
||
# Create a dedicated system user
|
||
sudo useradd --system --no-create-home --shell /bin/false loki
|
||
|
||
# Create directories
|
||
sudo mkdir -p /opt/loki/data /opt/loki/bin
|
||
|
||
# Download and extract Loki
|
||
sudo apt-get install -y unzip
|
||
cd /tmp
|
||
curl -LO https://github.com/grafana/loki/releases/download/v3.4.2/loki-linux-amd64.zip
|
||
unzip loki-linux-amd64.zip
|
||
sudo mv loki-linux-amd64 /opt/loki/bin/loki
|
||
sudo chmod +x /opt/loki/bin/loki
|
||
```
|
||
|
||
2. Download and install the production configuration file:
|
||
|
||
- [Download loki-config.yaml](/files/samples/loki/loki-config.yaml)
|
||
|
||
``` bash
|
||
sudo cp loki-config.yaml /opt/loki/loki-config.yaml
|
||
```
|
||
|
||
<div class="tip">
|
||
|
||
<div class="title">
|
||
|
||
Tip
|
||
|
||
</div>
|
||
|
||
**Log retention** is set to **14 days** by default. To change this, edit `/opt/loki/loki-config.yaml` and update the `retention_period` value under `limits_config`. Loki requires this value in hours — common values:
|
||
|
||
- `336h` = 14 days (default)
|
||
- `720h` = 30 days
|
||
- `2160h` = 90 days
|
||
- `8760h` = 365 days
|
||
|
||
Longer retention increases disk usage. As a rough guide, expect 1–3 GB per day for a moderately active Mattermost deployment (varies with log volume). Monitor `/opt/loki/data/` after the first week to project storage needs. The `compactor.retention_enabled` setting must remain `true` for retention enforcement to work.
|
||
|
||
</div>
|
||
|
||
3. Set ownership:
|
||
|
||
``` bash
|
||
sudo chown -R loki:loki /opt/loki
|
||
```
|
||
|
||
4. Create a systemd service file:
|
||
|
||
``` bash
|
||
sudo tee /etc/systemd/system/loki.service > /dev/null <<'EOF'
|
||
[Unit]
|
||
Description=Grafana Loki Log Aggregation
|
||
Documentation=https://grafana.com/docs/loki/latest/
|
||
After=network-online.target
|
||
Wants=network-online.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=loki
|
||
Group=loki
|
||
ExecStart=/opt/loki/bin/loki -config.file=/opt/loki/loki-config.yaml
|
||
Restart=on-failure
|
||
RestartSec=5
|
||
LimitNOFILE=65536
|
||
StandardOutput=journal
|
||
StandardError=journal
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
```
|
||
|
||
5. Start Loki:
|
||
|
||
``` bash
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now loki
|
||
```
|
||
|
||
6. Verify Loki is running:
|
||
|
||
``` bash
|
||
sudo systemctl status loki
|
||
curl -s http://localhost:3100/ready
|
||
```
|
||
|
||
The `/ready` endpoint should return `ready`. Loki may take 15–20 seconds after startup before it reports ready — this warmup delay is normal.
|
||
|
||
## Step 3: Install OpenTelemetry Collector on each Mattermost server
|
||
|
||
The OpenTelemetry (OTel) Collector runs on each Mattermost application server and pushes logs to Loki. Repeat these steps on **every Mattermost application server**.
|
||
|
||
<Important>
|
||
|
||
Replace `<LOKI_HOST>` with the IP address or hostname of your monitoring server, `<HOSTNAME>` with this server's hostname (e.g., `mm-app-01`), and `<SERVICE_NAME>` with the service type (e.g., `mattermost` or `postgres`).
|
||
|
||
</Important>
|
||
|
||
1. Install the OTel Collector Contrib distribution:
|
||
|
||
``` bash
|
||
# Add OTel Collector repository and install
|
||
# Commands below are for Ubuntu/Debian
|
||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.145.0/otelcol-contrib_0.145.0_linux_amd64.deb
|
||
sudo dpkg -i otelcol-contrib_0.145.0_linux_amd64.deb
|
||
```
|
||
|
||
<div class="note">
|
||
|
||
<div class="title">
|
||
|
||
Note
|
||
|
||
</div>
|
||
|
||
The installation process automatically creates a system user and group named `otelcol-contrib`.
|
||
|
||
</div>
|
||
|
||
2. Download and edit the OpenTelemetry Collector configuration:
|
||
|
||
- [Download otel-collector-config-mattermost.yaml](/files/samples/loki/otel-collector-config-mattermost.yaml)
|
||
|
||
``` bash
|
||
sudo cp otel-collector-config-mattermost.yaml /etc/otelcol-contrib/config.yaml
|
||
```
|
||
|
||
Edit `/etc/otelcol-contrib/config.yaml` and replace the placeholders:
|
||
|
||
- Replace `<LOKI_HOST>` with your monitoring server's address.
|
||
- Replace `<HOSTNAME>` with this server's hostname.
|
||
- Replace `<SERVICE_NAME>` with the service type (e.g., `mattermost`).
|
||
|
||
``` bash
|
||
sudo vi /etc/otelcol-contrib/config.yaml
|
||
```
|
||
|
||
3. Grant the collector read access to the Mattermost log directory:
|
||
|
||
``` bash
|
||
# Grant the collector access to read Mattermost logs.
|
||
# Substitute 'mattermost' for the group that owns your log file.
|
||
sudo usermod -aG mattermost otelcol-contrib
|
||
|
||
# Ensure logs are group-readable
|
||
sudo chmod 640 /opt/mattermost/logs/mattermost.log
|
||
sudo chmod g+rx /opt/mattermost/logs
|
||
```
|
||
|
||
4. Restart the service:
|
||
|
||
``` bash
|
||
sudo systemctl restart otelcol-contrib
|
||
sudo systemctl enable otelcol-contrib
|
||
```
|
||
|
||
5. Verify the collector is running and shipping logs:
|
||
|
||
``` bash
|
||
sudo systemctl status otelcol-contrib
|
||
sudo journalctl -u otelcol-contrib -f
|
||
```
|
||
|
||
## Step 4: Add Loki as a data source in Grafana
|
||
|
||
1. Log in to Grafana (default: `http://<monitoring-server>:3000`).
|
||
2. Navigate to **Administration \> Data Sources \> Add data source**.
|
||
3. Select **Loki** from the list.
|
||
4. Configure the connection:
|
||
- **Name**: `Loki`
|
||
- **URL**: `http://localhost:3100` (since Loki is colocated with Grafana)
|
||
5. Select **Save & test**. Grafana should confirm the data source is working.
|
||
|
||
## Step 5: Import the Mattermost Loki dashboard
|
||
|
||
A pre-built Grafana dashboard is recommended for monitoring your logs. This dashboard is provided as a basic example:
|
||
|
||
- [Download the Mattermost Loki Logs dashboard JSON](/files/samples/grafana-dashboards/mattermost-loki-logs.json)
|
||
|
||
<Note>
|
||
|
||
While the dashboard provides a useful starting point, users can gain full query flexibility using the **Explore** tab in Grafana to build custom LogQL queries on the fly.
|
||
|
||
</Note>
|
||
|
||
The dashboard provides:
|
||
|
||
- **Log Volume by Level** — stacked bar chart showing log rates by severity.
|
||
- **Error / Warning / Total counters** — stat panels for the selected time range.
|
||
- **HTTP 4xx/5xx count** — quick visibility into application-level HTTP errors.
|
||
- **Error Rate Over Time** — time series per instance for spotting error spikes.
|
||
- **Top Error Messages** — table ranking the most frequent errors.
|
||
- **Log Browser** — searchable, filterable log panel with level and free-text variables.
|
||
|
||
To import the dashboard:
|
||
|
||
1. In Grafana, go to **Dashboards \> New \> Import**.
|
||
2. Select **Upload JSON file** and choose the downloaded `mattermost-loki-logs.json` file.
|
||
3. On the import screen, select your **Loki** data source from the dropdown.
|
||
4. Select **Import**.
|
||
|
||
## Step 6: Useful LogQL queries
|
||
|
||
You can run these queries directly in **Grafana Explore** (select the Loki data source) or use them to build additional dashboard panels.
|
||
|
||
**Search all Mattermost logs:**
|
||
|
||
``` text
|
||
{service_name="mattermost"}
|
||
```
|
||
|
||
**Filter by log level:**
|
||
|
||
``` text
|
||
{service_name="mattermost"} | json | detected_level="error"
|
||
```
|
||
|
||
**Search for all HTTP 4xx responses:**
|
||
|
||
``` text
|
||
{service_name="mattermost"} | json | status_code >= 400
|
||
```
|
||
|
||
**Search for all HTTP 5xx responses:**
|
||
|
||
``` text
|
||
{service_name="mattermost"} | json | status_code >= 500
|
||
```
|
||
|
||
**Top 5 error messages over 5-minute windows:**
|
||
|
||
``` text
|
||
topk(5, sum(count_over_time({service_name="mattermost"} | json | detected_level="error" [5m])) by (msg))
|
||
```
|
||
|
||
**Count errors by message over 5-minute windows:**
|
||
|
||
``` text
|
||
sum(count_over_time({service_name="mattermost"} | json | detected_level="error" [5m])) by (msg)
|
||
```
|
||
|
||
**Free-text search (e.g., plugin errors):**
|
||
|
||
``` text
|
||
{service_name="mattermost"} |~ "plugin"
|
||
```
|
||
|
||
**Authentication-related log lines:**
|
||
|
||
``` text
|
||
{service_name="mattermost"} |~ "(?i)(auth|login|token|session)"
|
||
```
|
||
|
||
**Logs from a specific server instance:**
|
||
|
||
``` text
|
||
{service_name="mattermost", service_instance_id="mm-app-01"}
|
||
```
|
||
|
||
<Tip>
|
||
|
||
For a full LogQL reference, see the [Grafana Loki LogQL documentation](https://grafana.com/docs/loki/latest/query/).
|
||
|
||
</Tip>
|
||
|
||
## Optional: Add PostgreSQL log collection
|
||
|
||
If your PostgreSQL database runs on a dedicated server (not RDS), you can ship its logs to Loki as well. This is useful for correlating slow queries or database errors with Mattermost application events.
|
||
|
||
<Note>
|
||
|
||
A separate configuration file is provided for PostgreSQL log collection. Install the OpenTelemetry Collector on the PostgreSQL server using the same steps from **Step 3**, then use this configuration file instead of the Mattermost one:
|
||
|
||
- [Download otel-collector-config-postgres.yaml](/files/samples/loki/otel-collector-config-postgres.yaml)
|
||
|
||
The PostgreSQL configuration handles common Ubuntu/Debian JSON log paths (`/var/log/postgresql/*.json`). Replace the same `<LOKI_HOST>`, `<HOSTNAME>`, and `<SERVICE_NAME>` placeholders as described in Step 3.
|
||
|
||
</Note>
|
||
|
||
Once running, PostgreSQL logs appear in Grafana under the label `{service_name="postgres"}`:
|
||
|
||
``` text
|
||
{service_name="postgres"} | json | detected_level="error"
|
||
```
|
||
|
||
## Verification and troubleshooting
|
||
|
||
**End-to-end verification checklist:**
|
||
|
||
1. Loki is healthy:
|
||
|
||
``` bash
|
||
curl -s http://<monitoring-server>:3100/ready
|
||
# Expected: "ready"
|
||
```
|
||
|
||
2. OpenTelemetry Collector is shipping logs (run on each Mattermost server):
|
||
|
||
``` bash
|
||
sudo journalctl -u otelcol-contrib -n 100
|
||
# Look for export successful messages or lack of errors
|
||
```
|
||
|
||
3. Logs are visible in Grafana: Go to **Explore**, select the **Loki** data source, and run `{service_name="mattermost"}`. You should see recent log lines.
|
||
|
||
4. The dashboard loads: Open the **Mattermost Log Aggregation** dashboard and confirm panels are populated. (Note: You may need to update dashboard queries to use <code>service_name</code> and <code>service_instance_id</code>).
|
||
|
||
**Common issues:**
|
||
|
||
<table>
|
||
<colgroup>
|
||
<col style={{width: '40%'}} />
|
||
<col style={{width: '60%'}} />
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th>Symptom</th>
|
||
<th>Resolution</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>OTel Col target shows errors</td>
|
||
<td>Verify the file paths in the config match the actual log file location. Check file permissions (<code>otelcol-contrib</code> user must be able to read the file).</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>connection refused</code> on port 3100</td>
|
||
<td>Ensure Loki is running (<code>systemctl status loki</code>). Check firewall rules: <code>sudo iptables -L -n | grep 3100</code> or check your AWS security group allows TCP 3100 from the Mattermost servers.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Logs appear in Loki but fields aren't parsed</td>
|
||
<td>Confirm Mattermost is writing JSON-formatted logs (see Step 1). Check that the <code>json_parser</code> operator (for Postgres) or OTel processors are correctly configured.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>/ready</code> returns an error</td>
|
||
<td>Check Loki logs: <code>sudo journalctl -u loki -f</code>. Common cause: permissions on <code>/opt/loki/data/</code> — ensure the <code>loki</code> user owns the directory.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Old logs are not being deleted</td>
|
||
<td>Verify <code>compactor.retention_enabled: true</code> and <code>limits_config.retention_period</code> are both set in the Loki config. The compactor runs on the <code>compaction_interval</code> (default: 10 minutes) and applies a <code>retention_delete_delay</code> (default: 2 hours) before actually removing data.</td>
|
||
</tr>
|
||
<tr>
|
||
<td>High disk usage on the monitoring server</td>
|
||
<td>Review the retention period in the configuration. Consider reducing it or adding more disk. Check <code>du -sh /opt/loki/data/</code> to see current usage.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|