├── manifests
├── clusterissuer.yaml
└── dashboards.yaml
├── functions.sh
├── config.yaml
├── README.md
├── value-files
├── grafana.yaml
└── loki.yaml
├── install-grafana.sh
├── install-loki.sh
├── install.sh
├── install-k3s.sh
└── deps.sh
/manifests/clusterissuer.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: cert-manager.io/v1
2 | kind: ClusterIssuer
3 | metadata:
4 | name: letsencrypt-prod
5 | spec:
6 | acme:
7 | email: ${EMAIL}
8 | privateKeySecretRef:
9 | name: prod-issuer-account-key
10 | server: https://acme-v02.api.letsencrypt.org/directory
11 | solvers:
12 | - http01:
13 | ingress:
14 | class: nginx
15 |
--------------------------------------------------------------------------------
/functions.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function config_val() {
4 | cat config.yaml | yq -r ".$1"
5 | }
6 |
7 | function wait_for_resource() {
8 | until echo "$(kubectl get $1 -n $3)" | grep -q "$2"; do
9 | sleep 2
10 | echo "Waiting for $1/$2 in namespace $3"
11 | done
12 | }
13 |
14 | function wait_for_resource_rollout() {
15 | wait_for_resource $1 $2 $3
16 | kubectl rollout status $1/$2 -n $3
17 | }
18 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | baseDomain: ""
2 | email: example@domain.com # Only used if baseDomain is not ""
3 | k3s:
4 | version: v1.29.2+k3s1
5 | certManager:
6 | version: v1.14.4 # Do not modify
7 | ingressNginxController:
8 | version: v1.10.0 # Do not modify
9 | loki:
10 | version: 5.47.1 # Do not modify
11 | storage: 5Gi
12 | username: admin
13 | password: supersecurepassword
14 | exposedPort: 30001 # Only used if baseDomain is ""
15 | retention: 744h # 24 * 31 (1 Month)
16 | grafana:
17 | version: 7.3.7 # Do not modify
18 | username: admin
19 | password: verysecurepassword
20 | exposedPort: 30000 # Only used if baseDomain is ""
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fivem-loki-logging
2 | Automated logging setup for FiveM using Loki + Grafana with SSL certificates
3 |
4 |
5 |
6 |
7 | 
8 |
9 | ## Features
10 |
11 | - Everything automated and running on Kubernetes
12 | - Loki preconfigured as a Datasource in Grafana
13 | - Retention support
14 | - Loki behind authentication layer
15 | - Pre-configured SSL with valid certificates (if you have a domain)
16 | - Ready to be used with ox_lib
17 |
18 | ## TL;DR (Only if you know what you're doing)
19 |
20 | ```bash
21 | git clone https://github.com/iLLeniumStudios/fivem-loki-logging
22 | cd fivem-loki-logging
23 | ./install.sh
24 | ```
25 |
--------------------------------------------------------------------------------
/value-files/grafana.yaml:
--------------------------------------------------------------------------------
1 | testFramework:
2 | enabled: false
3 | persistence:
4 | enabled: true
5 | size: 2Gi
6 | datasources:
7 | datasources.yaml:
8 | apiVersion: 1
9 | datasources:
10 | - name: Loki
11 | type: loki
12 | url: http://loki-gateway.logging.svc.cluster.local
13 | uid: DZgflSOH0WCbt6HIdnNC
14 | isDefault: true
15 | editable: false
16 | access: proxy
17 | basicAuth: true
18 | basicAuthUser: ${LOKI_USERNAME}
19 | secureJsonData:
20 | basicAuthPassword: ${LOKI_PASSWORD}
21 | ingress:
22 | enabled: true
23 | annotations:
24 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
25 | cert-manager.io/cluster-issuer: letsencrypt-prod
26 | ingressClassName: nginx
27 | hosts:
28 | - ${GRAFANA_DOMAIN}
29 | tls:
30 | - secretName: grafana-tls
31 | hosts:
32 | - ${GRAFANA_DOMAIN}
33 | adminUser: ${GRAFANA_USERNAME}
34 | adminPassword: ${GRAFANA_PASSWORD}
35 | automountServiceAccountToken: true
36 | serviceAccount:
37 | automountServiceAccountToken: true
38 | sidecar:
39 | dashboards:
40 | enabled: true
41 |
--------------------------------------------------------------------------------
/install-grafana.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . functions.sh
4 |
5 | kubectl apply -f manifests/dashboards.yaml -n logging
6 |
7 | export LOKI_USERNAME=$(config_val "loki.username")
8 | export LOKI_PASSWORD=$(config_val "loki.password")
9 | export GRAFANA_DOMAIN="grafana.$(config_val 'baseDomain')"
10 | export GRAFANA_USERNAME=$(config_val "grafana.username")
11 | export GRAFANA_PASSWORD=$(config_val "grafana.password")
12 |
13 | envsubst < value-files/grafana.yaml > value-files/grafana.rendered.yaml
14 |
15 | if [[ "$(config_val 'baseDomain')" == "" ]]; then
16 | helm upgrade --install grafana grafana/grafana --version $(config_val "grafana.version") --namespace logging --values value-files/grafana.rendered.yaml \
17 | --set ingress.enabled=false \
18 | --set service.type=NodePort \
19 | --set service.nodePort=$(config_val "grafana.exposedPort")
20 | else
21 | helm upgrade --install grafana grafana/grafana --version $(config_val "grafana.version") --namespace logging --values value-files/grafana.rendered.yaml
22 | fi
23 |
24 | wait_for_resource_rollout deployment grafana logging
25 |
26 | rm -rf value-files/grafana.rendered.yaml
27 |
--------------------------------------------------------------------------------
/install-loki.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . functions.sh
4 |
5 | export LOKI_STORAGE=$(config_val "loki.storage")
6 | export LOKI_USERNAME=$(config_val "loki.username")
7 | export LOKI_PASSWORD=$(config_val "loki.password")
8 | export LOKI_DOMAIN="loki.$(config_val 'baseDomain')"
9 | export LOKI_RETENTION=$(config_val "loki.retention")
10 |
11 | envsubst < value-files/loki.yaml > value-files/loki.rendered.yaml
12 |
13 | if [[ "$(config_val 'baseDomain')" == "" ]]; then
14 | helm upgrade --install loki grafana/loki --version $(config_val "loki.version") --namespace logging --create-namespace --values value-files/loki.rendered.yaml \
15 | --set gateway.ingress.enabled=false \
16 | --set gateway.service.type=NodePort \
17 | --set gateway.service.nodePort=$(config_val "loki.exposedPort")
18 | else
19 | helm upgrade --install loki grafana/loki --version $(config_val "loki.version") --namespace logging --create-namespace --values value-files/loki.rendered.yaml
20 | fi
21 |
22 |
23 | wait_for_resource_rollout statefulset loki logging
24 | wait_for_resource_rollout deployment loki-gateway logging
25 |
26 | rm -rf value-files/loki.rendered.yaml
27 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . functions.sh
4 | . deps.sh
5 |
6 | ./install-k3s.sh
7 | ./install-loki.sh
8 | ./install-grafana.sh
9 |
10 | if [[ "$(config_val 'baseDomain')" == "" ]]; then
11 | IP=$(ip -f inet addr show "$(ip route get 8.8.8.8 | sed -n 's/.*dev \([^\ ]*\).*/\1/p')" | sed -En -e 's/.*inet ([0-9.]+).*/\1/p')
12 | LOKI_URL="$IP:$(config_val 'loki.exposedPort')"
13 | GRAFANA_URL="$IP:$(config_val 'grafana.exposedPort')"
14 | PROTOCOL="http"
15 | else
16 | LOKI_URL="loki.$(config_val 'baseDomain')"
17 | GRAFANA_URL="grafana.$(config_val 'baseDomain')"
18 | PROTOCOL="https"
19 | fi
20 | {
21 | printf 'Service\tURL\tUsername\tPassword\n';
22 | printf '%s\t%s\t%s\t%s\n' "Loki" ${PROTOCOL}://${LOKI_URL} $(config_val "loki.username") $(config_val "loki.password");
23 | printf '%s\t%s\t%s\t%s\n' "Grafana" ${PROTOCOL}://${GRAFANA_URL} $(config_val "grafana.username") $(config_val "grafana.password");
24 | } | prettytable 4
25 |
26 | printf "You can now add the following to your server.cfg in order to configure ox_lib Logger:\n\n"
27 | echo "set ox:logger \"loki\""
28 | echo "set loki:user \"$(config_val 'loki.username')\""
29 | echo "set loki:password \"$(config_val 'loki.password')\""
30 | echo "set loki:endpoint \"${PROTOCOL}://${LOKI_URL}\""
31 |
--------------------------------------------------------------------------------
/install-k3s.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . functions.sh
4 |
5 | if [ ! -f /etc/rancher/k3s/k3s.yaml ]; then
6 | echo "k3s not installed. Installing."
7 | curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=$(config_val "k3s.version") INSTALL_K3S_EXEC="--disable=traefik" sh -
8 |
9 | wait_for_resource_rollout deployment metrics-server kube-system
10 | wait_for_resource_rollout deployment coredns kube-system
11 | wait_for_resource_rollout deployment local-path-provisioner kube-system
12 | else
13 | echo "k3s already installed. Skipping."
14 | fi
15 |
16 | if [[ "$(config_val 'baseDomain')" == "" ]]; then
17 | echo "baseDomain is empty. Skipping cert-manager."
18 | else
19 | echo "baseDomain is not empty. Will install cert-manager and ingress-nginx controller."
20 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-$(config_val "ingressNginxController.version")/deploy/static/provider/cloud/deploy.yaml
21 | wait_for_resource_rollout deployment ingress-nginx-controller ingress-nginx
22 |
23 | helm upgrade --install cert-manager jetstack/cert-manager --version $(config_val "certManager.version") --namespace cert-manager --create-namespace --set installCRDs=true
24 |
25 | export EMAIL=$(config_val "email")
26 |
27 | envsubst < manifests/clusterissuer.yaml | kubectl apply -f -
28 |
29 | wait_for_resource_rollout deployment cert-manager cert-manager
30 | wait_for_resource_rollout deployment cert-manager-webhook cert-manager
31 | wait_for_resource_rollout deployment cert-manager-cainjector cert-manager
32 | fi
33 |
--------------------------------------------------------------------------------
/deps.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function ensure_helm_repos() {
4 | helm repo add jetstack https://charts.jetstack.io
5 | helm repo add grafana https://grafana.github.io/helm-charts
6 | helm repo update
7 | }
8 |
9 | function update_os() {
10 | apt update
11 | apt upgrade -y
12 | apt install -y curl wget git sudo lsb-release vim
13 | }
14 |
15 | function ensure_distro_specific_deps() {
16 | DISTRO=$(lsb_release -si)
17 | echo "Detected Distribution: $DISTRO"
18 | if [[ "$DISTRO" == "Ubuntu" ]]; then
19 | echo "Disabling ufw"
20 | ufw disable
21 | systemctl disable ufw
22 | elif [[ "$DISTRO" == "Debian" ]]; then
23 | echo "Updating iptables"
24 | apt remove -y iptables nftables
25 | apt install -y arptables ebtables
26 | update-alternatives --set iptables /usr/sbin/iptables-legacy
27 | update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
28 | update-alternatives --set arptables /usr/sbin/arptables-legacy
29 | update-alternatives --set ebtables /usr/sbin/ebtables-legacy
30 | else
31 | echo "Unsupported Distribution. Exiting..."
32 | exit 1
33 | fi
34 | }
35 |
36 | function ensure_deps() {
37 | if command -v yq >/dev/null 2>&1 ; then
38 | echo "yq is already installed. Skipping."
39 | else
40 | wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq
41 | chmod +x /usr/local/bin/yq
42 | fi
43 |
44 | if command -v helm >/dev/null 2>&1 ; then
45 | echo "helm is already installed. Skipping."
46 | else
47 | curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
48 | fi
49 |
50 | if command -v prettytable >/dev/null 2>&1 ; then
51 | echo "prettytable is already installed. Skipping."
52 | else
53 | wget https://raw.githubusercontent.com/jakobwesthoff/prettytable.sh/master/prettytable -O /usr/local/bin/prettytable
54 | chmod +x /usr/local/bin/prettytable
55 | fi
56 | }
57 |
58 | update_os
59 | ensure_deps
60 | ensure_distro_specific_deps
61 | ensure_helm_repos
62 |
63 |
64 | export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
65 |
--------------------------------------------------------------------------------
/value-files/loki.yaml:
--------------------------------------------------------------------------------
1 | singleBinary:
2 | replicas: 1
3 | targetModule: "all"
4 | persistence:
5 | size: ${LOKI_STORAGE}
6 | monitoring:
7 | dashboards:
8 | enabled: false
9 | rules:
10 | enabled: false
11 | serviceMonitor:
12 | enabled: false
13 | selfMonitoring:
14 | enabled: false
15 | grafanaAgent:
16 | installOperator: false
17 | lokiCanary:
18 | enabled: false
19 | test:
20 | enabled: false
21 | loki:
22 | commonConfig:
23 | replication_factor: 1
24 | storage:
25 | type: filesystem
26 | auth_enabled: false
27 | structuredConfig:
28 | storage_config:
29 | tsdb_shipper:
30 | shared_store: filesystem
31 | limits_config:
32 | max_query_parallelism: 32
33 | retention_period: ${LOKI_RETENTION}
34 | tsdb_max_query_parallelism: 512
35 | table_manager:
36 | retention_deletes_enabled: true
37 | retention_period: ${LOKI_RETENTION}
38 | querier:
39 | max_concurrent: 16
40 | schema_config:
41 | configs:
42 | - from: "2020-09-07"
43 | store: boltdb-shipper
44 | object_store: filesystem
45 | schema: v11
46 | index:
47 | prefix: index_
48 | period: 24h
49 | - from: "2023-04-10"
50 | store: tsdb
51 | object_store: filesystem
52 | schema: v12
53 | index:
54 | prefix: index_
55 | period: 24h
56 | query_scheduler:
57 | max_outstanding_requests_per_tenant: 32768
58 | gateway:
59 | deploymentStrategy:
60 | type: Recreate
61 | basicAuth:
62 | enabled: true
63 | username: ${LOKI_USERNAME}
64 | password: ${LOKI_PASSWORD}
65 | ingress:
66 | enabled: true
67 | annotations:
68 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
69 | cert-manager.io/cluster-issuer: letsencrypt-prod
70 | ingressClassName: nginx
71 | hosts:
72 | - host: ${LOKI_DOMAIN}
73 | paths:
74 | - path: /
75 | pathType: ImplementationSpecific
76 | tls:
77 | - secretName: loki-gateway-tls
78 | hosts:
79 | - ${LOKI_DOMAIN}
80 |
--------------------------------------------------------------------------------
/manifests/dashboards.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: simple-logs-dashboard
5 | namespace: logging
6 | labels:
7 | grafana_dashboard: "1"
8 | data:
9 | simple-logs-dashboard.json: |
10 | {
11 | "annotations": {
12 | "list": [
13 | {
14 | "builtIn": 1,
15 | "datasource": {
16 | "type": "grafana",
17 | "uid": "-- Grafana --"
18 | },
19 | "enable": true,
20 | "hide": true,
21 | "iconColor": "rgba(0, 211, 255, 1)",
22 | "name": "Annotations & Alerts",
23 | "target": {
24 | "limit": 100,
25 | "matchAny": false,
26 | "tags": [],
27 | "type": "dashboard"
28 | },
29 | "type": "dashboard"
30 | }
31 | ]
32 | },
33 | "editable": true,
34 | "fiscalYearStartMonth": 0,
35 | "graphTooltip": 0,
36 | "links": [],
37 | "liveNow": false,
38 | "panels": [
39 | {
40 | "datasource": {
41 | "type": "loki",
42 | "uid": "DZgflSOH0WCbt6HIdnNC"
43 | },
44 | "fieldConfig": {
45 | "defaults": {
46 | "color": {
47 | "mode": "palette-classic"
48 | },
49 | "custom": {
50 | "axisCenteredZero": false,
51 | "axisColorMode": "text",
52 | "axisLabel": "",
53 | "axisPlacement": "auto",
54 | "fillOpacity": 80,
55 | "gradientMode": "none",
56 | "hideFrom": {
57 | "legend": false,
58 | "tooltip": false,
59 | "viz": false
60 | },
61 | "lineWidth": 1,
62 | "scaleDistribution": {
63 | "type": "linear"
64 | },
65 | "thresholdsStyle": {
66 | "mode": "off"
67 | }
68 | },
69 | "mappings": [],
70 | "thresholds": {
71 | "mode": "absolute",
72 | "steps": [
73 | {
74 | "color": "green",
75 | "value": null
76 | },
77 | {
78 | "color": "red",
79 | "value": 80
80 | }
81 | ]
82 | }
83 | },
84 | "overrides": []
85 | },
86 | "gridPos": {
87 | "h": 7,
88 | "w": 12,
89 | "x": 0,
90 | "y": 0
91 | },
92 | "id": 7,
93 | "interval": "1m",
94 | "options": {
95 | "barRadius": 0,
96 | "barWidth": 0.97,
97 | "fullHighlight": false,
98 | "groupWidth": 0.7,
99 | "legend": {
100 | "calcs": [],
101 | "displayMode": "list",
102 | "placement": "bottom",
103 | "showLegend": true
104 | },
105 | "orientation": "auto",
106 | "showValue": "auto",
107 | "stacking": "none",
108 | "tooltip": {
109 | "mode": "single",
110 | "sort": "none"
111 | },
112 | "xTickLabelRotation": 0,
113 | "xTickLabelSpacing": 0
114 | },
115 | "targets": [
116 | {
117 | "datasource": {
118 | "type": "loki",
119 | "uid": "DZgflSOH0WCbt6HIdnNC"
120 | },
121 | "editorMode": "code",
122 | "expr": "count(count_over_time({event=~\".+\"}[$__interval]))",
123 | "legendFormat": "Logs",
124 | "queryType": "range",
125 | "refId": "A"
126 | }
127 | ],
128 | "title": "Log Ingestion Count over time",
129 | "type": "barchart"
130 | },
131 | {
132 | "datasource": {
133 | "type": "loki",
134 | "uid": "DZgflSOH0WCbt6HIdnNC"
135 | },
136 | "fieldConfig": {
137 | "defaults": {
138 | "color": {
139 | "mode": "palette-classic"
140 | },
141 | "custom": {
142 | "axisCenteredZero": false,
143 | "axisColorMode": "text",
144 | "axisLabel": "",
145 | "axisPlacement": "auto",
146 | "fillOpacity": 80,
147 | "gradientMode": "none",
148 | "hideFrom": {
149 | "legend": false,
150 | "tooltip": false,
151 | "viz": false
152 | },
153 | "lineWidth": 1,
154 | "scaleDistribution": {
155 | "type": "linear"
156 | },
157 | "thresholdsStyle": {
158 | "mode": "off"
159 | }
160 | },
161 | "mappings": [],
162 | "thresholds": {
163 | "mode": "absolute",
164 | "steps": [
165 | {
166 | "color": "green",
167 | "value": null
168 | },
169 | {
170 | "color": "red",
171 | "value": 80
172 | }
173 | ]
174 | },
175 | "unit": "bytes"
176 | },
177 | "overrides": []
178 | },
179 | "gridPos": {
180 | "h": 7,
181 | "w": 12,
182 | "x": 12,
183 | "y": 0
184 | },
185 | "id": 8,
186 | "interval": "1m",
187 | "options": {
188 | "barRadius": 0,
189 | "barWidth": 0.97,
190 | "fullHighlight": false,
191 | "groupWidth": 0.7,
192 | "legend": {
193 | "calcs": [],
194 | "displayMode": "list",
195 | "placement": "bottom",
196 | "showLegend": true
197 | },
198 | "orientation": "auto",
199 | "showValue": "auto",
200 | "stacking": "none",
201 | "tooltip": {
202 | "mode": "single",
203 | "sort": "none"
204 | },
205 | "xTickLabelRotation": 0,
206 | "xTickLabelSpacing": 0
207 | },
208 | "targets": [
209 | {
210 | "datasource": {
211 | "type": "loki",
212 | "uid": "DZgflSOH0WCbt6HIdnNC"
213 | },
214 | "editorMode": "code",
215 | "expr": "sum(bytes_over_time({event=~\".+\"}[$__interval]))",
216 | "legendFormat": "Size",
217 | "queryType": "range",
218 | "refId": "A"
219 | }
220 | ],
221 | "title": "Log Ingestion Size over time",
222 | "type": "barchart"
223 | },
224 | {
225 | "datasource": {
226 | "type": "loki",
227 | "uid": "DZgflSOH0WCbt6HIdnNC"
228 | },
229 | "fieldConfig": {
230 | "defaults": {
231 | "color": {
232 | "mode": "thresholds"
233 | },
234 | "custom": {
235 | "align": "auto",
236 | "cellOptions": {
237 | "type": "auto"
238 | },
239 | "inspect": false
240 | },
241 | "mappings": [],
242 | "thresholds": {
243 | "mode": "absolute",
244 | "steps": [
245 | {
246 | "color": "green",
247 | "value": null
248 | },
249 | {
250 | "color": "red",
251 | "value": 80
252 | }
253 | ]
254 | }
255 | },
256 | "overrides": [
257 | {
258 | "matcher": {
259 | "id": "byName",
260 | "options": "Time"
261 | },
262 | "properties": [
263 | {
264 | "id": "custom.hidden",
265 | "value": true
266 | }
267 | ]
268 | },
269 | {
270 | "matcher": {
271 | "id": "byName",
272 | "options": "Value #A"
273 | },
274 | "properties": [
275 | {
276 | "id": "displayName",
277 | "value": "Count"
278 | }
279 | ]
280 | },
281 | {
282 | "matcher": {
283 | "id": "byName",
284 | "options": "resource"
285 | },
286 | "properties": [
287 | {
288 | "id": "displayName",
289 | "value": "Resource"
290 | }
291 | ]
292 | }
293 | ]
294 | },
295 | "gridPos": {
296 | "h": 12,
297 | "w": 5,
298 | "x": 0,
299 | "y": 7
300 | },
301 | "id": 2,
302 | "options": {
303 | "footer": {
304 | "countRows": false,
305 | "fields": "",
306 | "reducer": [
307 | "sum"
308 | ],
309 | "show": false
310 | },
311 | "showHeader": true,
312 | "sortBy": [
313 | {
314 | "desc": true,
315 | "displayName": "Count"
316 | }
317 | ]
318 | },
319 | "pluginVersion": "9.4.7",
320 | "targets": [
321 | {
322 | "datasource": {
323 | "type": "loki",
324 | "uid": "DZgflSOH0WCbt6HIdnNC"
325 | },
326 | "editorMode": "code",
327 | "expr": "sum(count_over_time({event=~\".+\"}[$__range])) by (resource)",
328 | "queryType": "instant",
329 | "refId": "A"
330 | }
331 | ],
332 | "title": "Resources",
333 | "transformations": [],
334 | "type": "table"
335 | },
336 | {
337 | "datasource": {
338 | "type": "loki",
339 | "uid": "DZgflSOH0WCbt6HIdnNC"
340 | },
341 | "gridPos": {
342 | "h": 24,
343 | "w": 19,
344 | "x": 5,
345 | "y": 7
346 | },
347 | "id": 5,
348 | "options": {
349 | "dedupStrategy": "none",
350 | "enableLogDetails": true,
351 | "prettifyLogMessage": false,
352 | "showCommonLabels": false,
353 | "showLabels": false,
354 | "showTime": false,
355 | "sortOrder": "Descending",
356 | "wrapLogMessage": false
357 | },
358 | "pluginVersion": "9.4.7",
359 | "targets": [
360 | {
361 | "datasource": {
362 | "type": "loki",
363 | "uid": "DZgflSOH0WCbt6HIdnNC"
364 | },
365 | "editorMode": "code",
366 | "expr": "{event=~\".+\"} |= `$search` | json | line_format \"{{(printf \\\"%s\\\" .message)}}\"",
367 | "queryType": "range",
368 | "refId": "A"
369 | }
370 | ],
371 | "title": "Logs",
372 | "transformations": [],
373 | "type": "logs"
374 | },
375 | {
376 | "datasource": {
377 | "type": "loki",
378 | "uid": "DZgflSOH0WCbt6HIdnNC"
379 | },
380 | "fieldConfig": {
381 | "defaults": {
382 | "color": {
383 | "mode": "thresholds"
384 | },
385 | "custom": {
386 | "align": "auto",
387 | "cellOptions": {
388 | "type": "auto"
389 | },
390 | "inspect": false
391 | },
392 | "mappings": [],
393 | "thresholds": {
394 | "mode": "absolute",
395 | "steps": [
396 | {
397 | "color": "green",
398 | "value": null
399 | },
400 | {
401 | "color": "red",
402 | "value": 80
403 | }
404 | ]
405 | }
406 | },
407 | "overrides": [
408 | {
409 | "matcher": {
410 | "id": "byName",
411 | "options": "Time"
412 | },
413 | "properties": [
414 | {
415 | "id": "custom.hidden",
416 | "value": true
417 | }
418 | ]
419 | },
420 | {
421 | "matcher": {
422 | "id": "byName",
423 | "options": "Value #A"
424 | },
425 | "properties": [
426 | {
427 | "id": "displayName",
428 | "value": "Count"
429 | }
430 | ]
431 | },
432 | {
433 | "matcher": {
434 | "id": "byName",
435 | "options": "event"
436 | },
437 | "properties": [
438 | {
439 | "id": "displayName",
440 | "value": "Event"
441 | }
442 | ]
443 | }
444 | ]
445 | },
446 | "gridPos": {
447 | "h": 12,
448 | "w": 5,
449 | "x": 0,
450 | "y": 19
451 | },
452 | "id": 3,
453 | "options": {
454 | "footer": {
455 | "countRows": false,
456 | "fields": "",
457 | "reducer": [
458 | "sum"
459 | ],
460 | "show": false
461 | },
462 | "showHeader": true,
463 | "sortBy": [
464 | {
465 | "desc": true,
466 | "displayName": "Count"
467 | }
468 | ]
469 | },
470 | "pluginVersion": "9.4.7",
471 | "targets": [
472 | {
473 | "datasource": {
474 | "type": "loki",
475 | "uid": "DZgflSOH0WCbt6HIdnNC"
476 | },
477 | "editorMode": "code",
478 | "expr": "sum(count_over_time({event=~\".+\"}[$__range])) by (event)",
479 | "queryType": "instant",
480 | "refId": "A"
481 | }
482 | ],
483 | "title": "Events",
484 | "transformations": [],
485 | "type": "table"
486 | }
487 | ],
488 | "refresh": "",
489 | "revision": 1,
490 | "schemaVersion": 38,
491 | "style": "dark",
492 | "tags": [],
493 | "templating": {
494 | "list": [
495 | {
496 | "datasource": {
497 | "type": "loki",
498 | "uid": "DZgflSOH0WCbt6HIdnNC"
499 | },
500 | "filters": [],
501 | "hide": 0,
502 | "name": "Filters",
503 | "skipUrlSync": false,
504 | "type": "adhoc"
505 | },
506 | {
507 | "current": {
508 | "selected": false,
509 | "text": "",
510 | "value": ""
511 | },
512 | "hide": 0,
513 | "label": "Search",
514 | "name": "search",
515 | "options": [
516 | {
517 | "selected": true,
518 | "text": "",
519 | "value": ""
520 | }
521 | ],
522 | "query": "",
523 | "skipUrlSync": false,
524 | "type": "textbox"
525 | }
526 | ]
527 | },
528 | "time": {
529 | "from": "now-24h",
530 | "to": "now"
531 | },
532 | "timepicker": {},
533 | "timezone": "",
534 | "title": "Simple Logs Dashboard",
535 | "uid": "nzrEnMyVz",
536 | "version": 1,
537 | "weekStart": ""
538 | }
539 |
--------------------------------------------------------------------------------