Skip to content

Commit 66c96e0

Browse files
Feature | Add middleware for OpenTelemetry (#95)
* feat: initial opentelemetry configuration and docker initialization * feat: Add middleware * chore: include middleware in every request * chore: add check config for enabled otel * chore: add changes requested * chore: Add PR's changes requested --------- Co-authored-by: Matias Perrone <github@matiasperrone.com>
1 parent f13ba45 commit 66c96e0

9 files changed

Lines changed: 1751 additions & 122 deletions

File tree

.env.example

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,40 @@ DEFAULT_PROFILE_IMAGE=
112112
AUTH_PASSWORD_RESET_LIFETIME=1800
113113

114114
AUTH_PASSWORD_SHAPE_PATTERN="^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-])"
115-
AUTH_PASSWORD_SHAPE_WARNING="Password must include at least one uppercase letter, one lowercase letter, one number, and one special character."
115+
AUTH_PASSWORD_SHAPE_WARNING="Password must include at least one uppercase letter, one lowercase letter, one number, and one special character."
116+
117+
118+
#Open Telemetry
119+
OTEL_SERVICE_ENABLED=true
120+
OTEL_SERVICE_NAME=idp-api
121+
OTEL_PROPAGATORS=tracecontext,baggage
122+
OTEL_EXPORTER_OTLP_PROTOCOL=http/json # Supported values: "grpc", "http/protobuf", "http/json"
123+
OTEL_EXPORTER_OTLP_MAX_RETRIES=3
124+
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
125+
TRACE_SPAN_PREFIX=SPAN
126+
OTEL_TRACES_SAMPLER_PARENT=false
127+
# OTEL_TRACES_SAMPLER_TYPE=always_on # Supported values: "always_on", "always_off", "traceidratio"
128+
# OTEL_TRACES_SAMPLER_TRACEIDRATIO_RATIO=0.05
129+
# OTEL_METRICS_EXPORTER=otlp
130+
# OTEL_TRACES_EXPORTER=otlp
131+
# OTEL_LOGS_EXPORTER=otlp
132+
# OTEL_EXPORTER_OTLP_TIMEOUT=10000
133+
# OTEL_EXPORTER_OTLP_HEADERS=
134+
# OTEL_EXPORTER_OTLP_TRACES_TIMEOUT=10000
135+
# OTEL_EXPORTER_OTLP_TRACES_HEADERS=
136+
# OTEL_EXPORTER_OTLP_METRICS_TIMEOUT=10000
137+
# OTEL_EXPORTER_OTLP_METRICS_HEADERS=
138+
# OTEL_EXPORTER_OTLP_LOGS_TIMEOUT=10000
139+
# OTEL_EXPORTER_OTLP_LOGS_HEADERS=
140+
# OTEL_EXPORTER_ZIPKIN_ENDPOINT=http://localhost:9411
141+
# OTEL_EXPORTER_ZIPKIN_TIMEOUT=10000
142+
# OTEL_EXPORTER_ZIPKIN_MAX_RETRIES=3
143+
# OTEL_INSTRUMENTATION_HTTP_SERVER=true
144+
# OTEL_INSTRUMENTATION_HTTP_CLIENT=true
145+
# OTEL_INSTRUMENTATION_QUERY=true
146+
# OTEL_INSTRUMENTATION_REDIS=true
147+
# OTEL_INSTRUMENTATION_QUEUE=true
148+
# OTEL_INSTRUMENTATION_CACHE=true
149+
# OTEL_INSTRUMENTATION_VIEW=true
150+
# OTEL_INSTRUMENTATION_LIVEWIRE=true
151+
# OTEL_INSTRUMENTATION_CONSOLE=true

app/Http/Kernel.php

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<?php namespace App\Http;
1+
<?php
2+
namespace App\Http;
23
/**
34
* Copyright 2016 OpenStack Foundation
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,6 +30,7 @@ class Kernel extends HttpKernel
2930
*/
3031
protected $middleware = [
3132
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
33+
\App\Http\Middleware\TrackRequestMiddleware::class,
3234
\App\Http\Middleware\SingleAccessPoint::class,
3335
\Illuminate\Http\Middleware\HandleCors::class,
3436
\App\Http\Middleware\ParseMultipartFormDataInputForNonPostRequests::class,
@@ -67,19 +69,19 @@ class Kernel extends HttpKernel
6769
* @var array
6870
*/
6971
protected $routeMiddleware = [
70-
'auth' => \App\Http\Middleware\Authenticate::class,
71-
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
72-
'ssl' => \App\Http\Middleware\SSLMiddleware::class,
73-
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
74-
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
75-
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
76-
'oauth2.endpoint' => \App\Http\Middleware\OAuth2BearerAccessTokenRequestValidator::class,
77-
'oauth2.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdmin::class,
78-
'oauth2.currentuser.serveradmin.json' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdminJson::class,
79-
'openstackid.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOpenIdServerAdmin::class,
72+
'auth' => \App\Http\Middleware\Authenticate::class,
73+
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
74+
'ssl' => \App\Http\Middleware\SSLMiddleware::class,
75+
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
76+
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
77+
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
78+
'oauth2.endpoint' => \App\Http\Middleware\OAuth2BearerAccessTokenRequestValidator::class,
79+
'oauth2.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdmin::class,
80+
'oauth2.currentuser.serveradmin.json' => \App\Http\Middleware\CurrentUserIsOAuth2ServerAdminJson::class,
81+
'openstackid.currentuser.serveradmin' => \App\Http\Middleware\CurrentUserIsOpenIdServerAdmin::class,
8082
'openstackid.currentuser.serveradmin.json' => \App\Http\Middleware\CurrentUserIsOpenIdServerAdminJson::class,
81-
'oauth2.currentuser.allow.client.edition' => \App\Http\Middleware\CurrentUserCanEditOAuth2Client::class,
82-
'oauth2.currentuser.owns.client' => \App\Http\Middleware\CurrentUserOwnsOAuth2Client::class,
83-
'service.account' => \App\Http\Middleware\EnsureServiceAccount::class,
83+
'oauth2.currentuser.allow.client.edition' => \App\Http\Middleware\CurrentUserCanEditOAuth2Client::class,
84+
'oauth2.currentuser.owns.client' => \App\Http\Middleware\CurrentUserOwnsOAuth2Client::class,
85+
'service.account' => \App\Http\Middleware\EnsureServiceAccount::class,
8486
];
85-
}
87+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
use Keepsuit\LaravelOpenTelemetry\Facades\Tracer;
9+
use Illuminate\Log\LogManager;
10+
use OpenTelemetry\API\Baggage\Baggage;
11+
use OpenTelemetry\Context\ScopeInterface;
12+
13+
class TrackRequestMiddleware
14+
{
15+
private const ATTRIBUTE_START_TIME = '_start_time';
16+
private const EVENT_REQUEST_STARTED = 'request.started';
17+
private const EVENT_REQUEST_FINISHED = 'request.finished';
18+
19+
protected LogManager $logger;
20+
private ?ScopeInterface $baggageScope = null;
21+
private bool $shouldTrack;
22+
23+
public function __construct(LogManager $logger)
24+
{
25+
$this->logger = $logger;
26+
$this->shouldTrack = config('opentelemetry.enabled', env('OTEL_SERVICE_ENABLED', false));
27+
}
28+
29+
public function handle(Request $request, Closure $next)
30+
{
31+
if (!$this->shouldTrack) {
32+
return $next($request);
33+
}
34+
35+
try {
36+
$request->attributes->set(self::ATTRIBUTE_START_TIME, microtime(true));
37+
if ($span = Tracer::activeSpan()) {
38+
if ($ray = $request->header('cf-ray')) {
39+
$span->setAttribute('cloudflare.cf-ray', $ray);
40+
41+
$baggage = Baggage::getCurrent()
42+
->toBuilder()
43+
->set('cf-ray', $ray)
44+
->set('user_agent', substr($request->userAgent() ?? 'unknown', 0, 100))
45+
->build();
46+
47+
$this->baggageScope = $baggage->activate();
48+
}
49+
50+
$span->addEvent(self::EVENT_REQUEST_STARTED, [
51+
'method' => $request->method(),
52+
'url' => $request->fullUrl(),
53+
]);
54+
}
55+
} catch (\Throwable $e) {
56+
$this->logger->channel('daily')->error("Error on request tracking: " . $e->getMessage());
57+
}
58+
59+
return $next($request);
60+
}
61+
62+
public function terminate(Request $request, Response $response): void
63+
{
64+
if (!$this->shouldTrack) {
65+
return;
66+
}
67+
68+
try {
69+
$start = (float) $request->attributes->get(self::ATTRIBUTE_START_TIME, microtime(true));
70+
$ms = (int) ((microtime(true) - $start) * 1000);
71+
72+
if ($span = Tracer::activeSpan()) {
73+
$span->setAttribute('app.response_ms', $ms);
74+
$span->setAttribute('http.status_code', $response->getStatusCode());
75+
$span->addEvent(self::EVENT_REQUEST_FINISHED, ['response_ms' => $ms]);
76+
}
77+
} catch (\Throwable $e) {
78+
$this->logger->channel('daily')->error("Error on request tracking: " . $e->getMessage());
79+
} finally {
80+
if ($this->baggageScope) {
81+
$this->baggageScope->detach();
82+
$this->baggageScope = null;
83+
}
84+
}
85+
}
86+
}

composer.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"guzzlehttp/uri-template": "^1.0",
3434
"ircmaxell/random-lib": "1.2.0",
3535
"jenssegers/agent": "2.6.3",
36+
"keepsuit/laravel-opentelemetry": "^1.14",
3637
"laminas/laminas-crypt": "3.11.0",
3738
"laminas/laminas-math": "3.7.0",
3839
"laravel-doctrine/extensions": "2.0.1",
@@ -69,9 +70,9 @@
6970
"phpunit/phpunit": "^11.0.1",
7071
"rector/rector": "^2.0"
7172
},
72-
"suggest":{
73+
"suggest": {
7374
"lib-openssl": "Required to use AES algorithms (except AES GCM)",
74-
"ext-json":"Required to use json algorithms"
75+
"ext-json": "Required to use json algorithms"
7576
},
7677
"autoload": {
7778
"classmap": [
@@ -88,7 +89,10 @@
8889
"Utils\\": "app/libs/Utils/",
8990
"Models\\": "app/Models/",
9091
"Database\\Factories\\": "database/factories/",
91-
"Database\\Seeders\\": "database/seeders/"
92+
"Database\\Seeders\\": "database/seeders/",
93+
"OpenTelemetry\\SemConv\\Unstable\\Metrics\\": "vendor/open-telemetry/sem-conv/Incubating/Metrics/",
94+
"OpenTelemetry\\": "vendor/open-telemetry/",
95+
"OpenTelemetry\\API\\": "vendor/open-telemetry/api/"
9296
},
9397
"files": [
9498
"app/Utils/helpers.php",
@@ -102,8 +106,7 @@
102106
},
103107
"extra": {
104108
"laravel": {
105-
"dont-discover": [
106-
]
109+
"dont-discover": []
107110
}
108111
},
109112
"scripts": {
@@ -128,7 +131,9 @@
128131
"sort-packages": true,
129132
"optimize-autoloader": true,
130133
"allow-plugins": {
131-
"composer/package-versions-deprecated": true
134+
"composer/package-versions-deprecated": true,
135+
"php-http/discovery": true,
136+
"tbachert/spi": true
132137
}
133138
},
134139
"minimum-stability": "dev",

0 commit comments

Comments
 (0)