This library follows the PSR-12 convention.
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
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
]
]
)
]
);
The following options are not customizable in the configuration:
CURLOPT_CUSTOMREQUEST
CURLOPT_RETURNTRANSFER
CURLOPT_POSTFIELDS
CURLOPT_HTTPHEADER
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.
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)
'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', '')
],
],
],
],
],
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
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', '')
],
]
],
],
],
In order 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 -d
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
and replace the ip in the LOKI_ENTRYPOINT definition in phpunit.xml :
<env name="LOKI_ENTRYPOINT" value="http://172.17.0.1:7000/" />
If you're testing from containerized php not in the default docker bridge network, 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:{the_ip_of_your_host_in_your_network}
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:7000/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\":\"{\\\"stdClass\\\":[]}\",\"ctxt_foo\":\"34\"}"
]
]
}