graftak / monolog-loki
Monolog handler for Grafana Loki (BC fork!)
Requires
- php: >=7.4
- ext-curl: *
- ext-json: *
- monolog/monolog: ~1.25|~2.0
Requires (Dev)
- pdepend/pdepend: ~2.7
- phploc/phploc: ~6.0
- roave/security-advisories: dev-master
- sebastian/phpcpd: ~5.0
- squizlabs/php_codesniffer: ~3.5
- symfony/phpunit-bridge: ~5.1
README
! ! ! PLEASE NOTE ! ! !
This is a fork from itspire/monolog-loki for older projects still using PHP >=7.4 and Monolog 2, but with the bugfixes/improvements since version 2.0.0 applied to it. Please use https://github.com/itspire/monolog-loki or use this with care!
This library follows the PSR-12 convention.
Usage
Recommended Usage
Since Loki log handling uses a remote server, logging is prone to be subject of timeout, network shortage and so on. To avoid your application being broken in such a case, we recommend wrapping the handler in a WhatFailureGroupHandler
Native
use Itspire\MonologLoki\Handler\LokiHandler; use Monolog\Handler\WhatFailureGroupHandler; $handler = new WhatFailureGroupHandler( [ new LokiHandler( [ 'entrypoint' => 'https://loki:3100', 'context' => [ // Set here your globally applicable context variables ], 'labels' => [ // Set here your globally applicable labels ], 'client_name' => 'your_host_name', // Here set a unique identifier for the client host // Optional: Sets tenant id (HTTP header X-Scope-OrgID), if null or missing -> no header 'tenant_id' => 'some-tenant', // Optional: if you're using basic auth to authentify 'auth' => [ 'basic' => ['user', 'password'], ], // Optional: Override the default curl options with custom values 'curl_options' => [ CURLOPT_CONNECTTIMEOUT_MS => 500, CURLOPT_TIMEOUT_MS => 600 ] ] ) ] );
Non-customizable curl options
The following options are not customizable in the configuration:
CURLOPT_CUSTOMREQUEST
CURLOPT_RETURNTRANSFER
CURLOPT_POSTFIELDS
CURLOPT_HTTPHEADER
Symfony App
Configure LokiHandler Service
Itspire\MonologLoki\Handler\LokiHandler: arguments: $apiConfig: entrypoint: 'http://loki:3100' context: app: My-app labels: env: '%env(APP_ENV)%' client_name: my_app_server auth: basic: user: username password: password curl_options: !php/const CURLOPT_CONNECTTIMEOUT_MS: 500, !php/const CURLOPT_TIMEOUT_MS: 600
Note: We're currently working on a possible bundle-based implementation for Symfony but at the moment, this is the way.
Configure Monolog to use Loki Handler
monolog: handlers: loki: type: service id: Itspire\MonologLoki\Handler\LokiHandler my_loki_handler: type: whatfailuregroup members: [loki] level: debug process_psr_3_messages: true # optional but we find it rather useful (Note: native handler required to use)
Laravel App
Add Loki to config/logging.php
'loki' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => \Itspire\MonologLoki\Handler\LokiHandler::class, 'formatter' => \Itspire\MonologLoki\Formatter\LokiFormatter::class, 'formatter_with' => [ 'labels' => [], 'context' => [], 'systemName' => env('LOKI_SYSTEM_NAME', null), 'extraPrefix' => env('LOKI_EXTRA_PREFIX', ''), 'contextPrefix' => env('LOKI_CONTEXT_PREFIX', '') ], 'handler_with' => [ 'apiConfig' => [ 'entrypoint' => env('LOKI_ENTRYPOINT', "http://localhost:3100"), 'context' => [], 'labels' => [], 'client_name' => '', 'auth' => [ 'basic' => [ env('LOKI_AUTH_BASIC_USER', ''), env('LOKI_AUTH_BASIC_PASSWORD', '') ], ], ], ], ],
Set env vars
LOKI_ENTRYPOINT="http://loki:3100"
LOKI_AUTH_BASIC_USER=
LOKI_AUTH_BASIC_PASSWORD=
LOKI_SYSTEM_NAME=null
LOKI_CONTEXT_PREFIX="context_"
LOKI_EXTRA_PREFIX=
These vars can be injected by Kubernetes, Docker or simply by setting them on the .env file
Laravel with WhatFailureGroupHandler
Since Loki log handling uses a remote server, logging is prone to be subject of timeout, network shortage and so on. To avoid your application being broken in such a case, we recommend wrapping the handler in a WhatFailureGroupHandler.
Create a custom Log handler and wrap the LokiHandler
with a WhatFailureGroupHandler
.
namespace App\Logging; use Itspire\MonologLoki\Formatter\LokiFormatter; use Itspire\MonologLoki\Handler\LokiHandler; use Monolog\Handler\WhatFailureGroupHandler; use Monolog\Logger; class LokiNoFailureHandler { public function __invoke(array $config) { return new Logger('loki-no-failure', [ new WhatFailureGroupHandler([ (new LokiHandler($config['handler_with']['apiConfig'], $config['level'])) ->setFormatter(new LokiFormatter(...array_values($config['formatter_with']))) ]) ]); } }
Update the config accordingly:
parse_str(env('LOKI_LABELS', ''), $loki_formatter_labels); 'loki' => [ 'driver' => 'custom', 'level' => env('LOG_LEVEL', 'debug'), 'via' => \App\Logging\LokiNoFailureHandler::class, 'formatter_with' => [ // LOKI_LABELS: app=laravel&env=prod 'labels' => $loki_formatter_labels, 'context' => [], 'systemName' => env('LOKI_SYSTEM_NAME', ''), 'extraPrefix' => env('LOKI_EXTRA_PREFIX', ''), 'contextPrefix' => env('LOKI_CONTEXT_PREFIX', '') ], 'handler_with' => [ 'apiConfig' => [ 'entrypoint' => env('LOKI_ENTRYPOINT', "http://localhost:3100"), 'context' => [], 'labels' => [], 'client_name' => '', 'auth' => [ 'basic' => [ env('LOKI_AUTH_BASIC_USER', ''), env('LOKI_AUTH_BASIC_PASSWORD', '') ], ] ], ], ],
Testing
To test using the provided docker-compose file, you'll need an up-to-date docker/docker-compose installation You can start the Loki container by navigating to src/main/test/docker and running
docker-compose up
If you're testing from a local php installation, you'll need to retrieve the Loki container ip with :
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' itspire-monolog-loki_loki_1
If you're testing from containerized php, you'll need to start the container with an extra host named Loki mapped to your current host ip, using the following option :
--add-host loki:{your_host_ip}
Run the test using phpunit, and you can verify that posting to Loki works by running the following from your host terminal :
curl -G -s "http://localhost:3100/loki/api/v1/query" --data-urlencode 'query={channel="test"}' | jq
For each time you ran the tests, you should see a log entry looking like the following :
{ "stream": { "channel": "test", "host": "f2bbe48b0204", "level_name": "WARNING" }, "values": [ [ "1591627127000000000", "{\"message\":\"test\",\"level\":300,\"level_name\":\"WARNING\",\"channel\":\"test\",\"datetime\":\"2020-06-08 14:38:47\",\"ctxt_data\":\"[object] (stdClass: {})\",\"ctxt_foo\":\"34\"}" ] ] }