Skip to content

Commit 3b32d09

Browse files
feat: track Eloquent ORM (open-telemetry#339)
* feat: track Eloquent ORM * Update src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php Co-authored-by: Chris Lightfoot-Wild <[email protected]> * fix: move eval to fixture * chore: ignore false positive for unused class --------- Co-authored-by: Chris Lightfoot-Wild <[email protected]>
1 parent bfbfad7 commit 3b32d09

File tree

4 files changed

+382
-0
lines changed

4 files changed

+382
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\Illuminate\Database\Eloquent;
6+
7+
use Illuminate\Database\Eloquent\Model as EloquentModel;
8+
use OpenTelemetry\API\Trace\SpanKind;
9+
use OpenTelemetry\Context\Context;
10+
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\LaravelHook;
11+
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\LaravelHookTrait;
12+
use OpenTelemetry\Contrib\Instrumentation\Laravel\Hooks\PostHookTrait;
13+
use function OpenTelemetry\Instrumentation\hook;
14+
use OpenTelemetry\SemConv\TraceAttributes;
15+
use Throwable;
16+
17+
class Model implements LaravelHook
18+
{
19+
use LaravelHookTrait;
20+
use PostHookTrait;
21+
22+
public function instrument(): void
23+
{
24+
$this->hookFind();
25+
$this->hookPerformInsert();
26+
$this->hookPerformUpdate();
27+
$this->hookDelete();
28+
$this->hookGetModels();
29+
$this->hookDestroy();
30+
$this->hookRefresh();
31+
}
32+
33+
private function hookFind(): void
34+
{
35+
/** @psalm-suppress UnusedFunctionCall */
36+
hook(
37+
\Illuminate\Database\Eloquent\Builder::class,
38+
'find',
39+
pre: function ($builder, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
40+
$model = $builder->getModel();
41+
$builder = $this->instrumentation
42+
->tracer()
43+
->spanBuilder($model::class . '::find')
44+
->setSpanKind(SpanKind::KIND_INTERNAL)
45+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
46+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
47+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
48+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
49+
->setAttribute('laravel.eloquent.model', $model::class)
50+
->setAttribute('laravel.eloquent.table', $model->getTable())
51+
->setAttribute('laravel.eloquent.operation', 'find');
52+
53+
$parent = Context::getCurrent();
54+
$span = $builder->startSpan();
55+
Context::storage()->attach($span->storeInContext($parent));
56+
57+
return $params;
58+
},
59+
post: function ($builder, array $params, $result, ?Throwable $exception) {
60+
$this->endSpan($exception);
61+
}
62+
);
63+
}
64+
65+
private function hookPerformUpdate(): void
66+
{
67+
/** @psalm-suppress UnusedFunctionCall */
68+
hook(
69+
EloquentModel::class,
70+
'performUpdate',
71+
pre: function (EloquentModel $model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
72+
$builder = $this->instrumentation
73+
->tracer()
74+
->spanBuilder($model::class . '::update')
75+
->setSpanKind(SpanKind::KIND_INTERNAL)
76+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
77+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
78+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
79+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
80+
->setAttribute('laravel.eloquent.model', $model::class)
81+
->setAttribute('laravel.eloquent.table', $model->getTable())
82+
->setAttribute('laravel.eloquent.operation', 'update');
83+
84+
$parent = Context::getCurrent();
85+
$span = $builder->startSpan();
86+
Context::storage()->attach($span->storeInContext($parent));
87+
88+
return $params;
89+
},
90+
post: function (EloquentModel $model, array $params, $result, ?Throwable $exception) {
91+
$this->endSpan($exception);
92+
}
93+
);
94+
}
95+
96+
private function hookPerformInsert(): void
97+
{
98+
/** @psalm-suppress UnusedFunctionCall */
99+
hook(
100+
EloquentModel::class,
101+
'performInsert',
102+
pre: function (EloquentModel $model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
103+
$builder = $this->instrumentation
104+
->tracer()
105+
->spanBuilder($model::class . '::create')
106+
->setSpanKind(SpanKind::KIND_INTERNAL)
107+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
108+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
109+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
110+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
111+
->setAttribute('laravel.eloquent.model', $model::class)
112+
->setAttribute('laravel.eloquent.table', $model->getTable())
113+
->setAttribute('laravel.eloquent.operation', 'create');
114+
115+
$parent = Context::getCurrent();
116+
$span = $builder->startSpan();
117+
Context::storage()->attach($span->storeInContext($parent));
118+
119+
return $params;
120+
},
121+
post: function (EloquentModel $model, array $params, $result, ?Throwable $exception) {
122+
$this->endSpan($exception);
123+
}
124+
);
125+
}
126+
127+
private function hookDelete(): void
128+
{
129+
/** @psalm-suppress UnusedFunctionCall */
130+
hook(
131+
EloquentModel::class,
132+
'delete',
133+
pre: function (EloquentModel $model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
134+
$builder = $this->instrumentation
135+
->tracer()
136+
->spanBuilder($model::class . '::delete')
137+
->setSpanKind(SpanKind::KIND_INTERNAL)
138+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
139+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
140+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
141+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
142+
->setAttribute('laravel.eloquent.model', $model::class)
143+
->setAttribute('laravel.eloquent.table', $model->getTable())
144+
->setAttribute('laravel.eloquent.operation', 'delete');
145+
146+
$parent = Context::getCurrent();
147+
$span = $builder->startSpan();
148+
Context::storage()->attach($span->storeInContext($parent));
149+
150+
return $params;
151+
},
152+
post: function (EloquentModel $model, array $params, $result, ?Throwable $exception) {
153+
$this->endSpan($exception);
154+
}
155+
);
156+
}
157+
158+
private function hookGetModels(): void
159+
{
160+
/** @psalm-suppress UnusedFunctionCall */
161+
hook(
162+
\Illuminate\Database\Eloquent\Builder::class,
163+
'getModels',
164+
pre: function ($builder, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
165+
$model = $builder->getModel();
166+
$builder = $this->instrumentation
167+
->tracer()
168+
->spanBuilder($model::class . '::get')
169+
->setSpanKind(SpanKind::KIND_INTERNAL)
170+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
171+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
172+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
173+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
174+
->setAttribute('laravel.eloquent.model', $model::class)
175+
->setAttribute('laravel.eloquent.table', $model->getTable())
176+
->setAttribute('laravel.eloquent.operation', 'get')
177+
->setAttribute('db.statement', $builder->getQuery()->toSql());
178+
179+
$parent = Context::getCurrent();
180+
$span = $builder->startSpan();
181+
Context::storage()->attach($span->storeInContext($parent));
182+
183+
return $params;
184+
},
185+
post: function ($builder, array $params, $result, ?Throwable $exception) {
186+
$this->endSpan($exception);
187+
}
188+
);
189+
}
190+
191+
private function hookDestroy(): void
192+
{
193+
/** @psalm-suppress UnusedFunctionCall */
194+
hook(
195+
EloquentModel::class,
196+
'destroy',
197+
pre: function ($model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
198+
$builder = $this->instrumentation
199+
->tracer()
200+
->spanBuilder($model::class . '::destroy')
201+
->setSpanKind(SpanKind::KIND_INTERNAL)
202+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
203+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
204+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
205+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
206+
->setAttribute('laravel.eloquent.model', $model::class)
207+
->setAttribute('laravel.eloquent.table', $model->getTable())
208+
->setAttribute('laravel.eloquent.operation', 'destroy');
209+
210+
$parent = Context::getCurrent();
211+
$span = $builder->startSpan();
212+
Context::storage()->attach($span->storeInContext($parent));
213+
214+
return $params;
215+
},
216+
post: function ($model, array $params, $result, ?Throwable $exception) {
217+
$this->endSpan($exception);
218+
}
219+
);
220+
}
221+
222+
private function hookRefresh(): void
223+
{
224+
/** @psalm-suppress UnusedFunctionCall */
225+
hook(
226+
EloquentModel::class,
227+
'refresh',
228+
pre: function (EloquentModel $model, array $params, string $class, string $function, ?string $filename, ?int $lineno) {
229+
$builder = $this->instrumentation
230+
->tracer()
231+
->spanBuilder($model::class . '::refresh')
232+
->setSpanKind(SpanKind::KIND_INTERNAL)
233+
->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, $function)
234+
->setAttribute(TraceAttributes::CODE_NAMESPACE, $class)
235+
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename)
236+
->setAttribute(TraceAttributes::CODE_LINE_NUMBER, $lineno)
237+
->setAttribute('laravel.eloquent.model', $model::class)
238+
->setAttribute('laravel.eloquent.table', $model->getTable())
239+
->setAttribute('laravel.eloquent.operation', 'refresh');
240+
241+
$parent = Context::getCurrent();
242+
$span = $builder->startSpan();
243+
Context::storage()->attach($span->storeInContext($parent));
244+
245+
return $params;
246+
},
247+
post: function (EloquentModel $model, array $params, $result, ?Throwable $exception) {
248+
$this->endSpan($exception);
249+
}
250+
);
251+
}
252+
}

src/Instrumentation/Laravel/src/LaravelInstrumentation.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static function register(): void
2929
Hooks\Illuminate\Queue\SyncQueue::hook($instrumentation);
3030
Hooks\Illuminate\Queue\Queue::hook($instrumentation);
3131
Hooks\Illuminate\Queue\Worker::hook($instrumentation);
32+
Hooks\Illuminate\Database\Eloquent\Model::hook($instrumentation);
3233
}
3334

3435
public static function shouldTraceCli(): bool
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Fixtures\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
/**
10+
* @psalm-suppress UnusedClass
11+
*/
12+
class TestModel extends Model
13+
{
14+
protected $table = 'test_models';
15+
protected $fillable = ['name'];
16+
}

0 commit comments

Comments
 (0)