├── 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 |

Documentation

5 |
6 | 7 | ![image](https://user-images.githubusercontent.com/104288623/234932495-71277281-a2e1-4fc5-8ec1-e864ae177543.png) 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 | --------------------------------------------------------------------------------