# Laravel

> Learn how to integrate OpenTelemetry with Laravel applications and send telemetry data to Last9

Source: https://last9.io/docs/integrations/frameworks/php/laravel/

This guide shows you how to instrument your Laravel application with OpenTelemetry and send traces, metrics, and logs to Last9.

## Prerequisites

- PHP 8.1 or later
- Laravel 9.x or Laravel 10.x/11.x
- Composer
- Last9 account with OTLP endpoint configured

## Installation

1. **Install OpenTelemetry PHP extension**

   Install the PHP extension following the [OpenTelemetry PHP setup guide](https://opentelemetry.io/docs/zero-code/php/#install-the-opentelemetry-extension):

   ```bash
   # For Ubuntu/Debian
   sudo apt-get install php-dev
   pecl install opentelemetry

   # For macOS with Homebrew
   brew install php
   pecl install opentelemetry

   # Add to php.ini
   echo "extension=opentelemetry" >> $(php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||")
   ```

2. **Install OpenTelemetry SDK and auto-instrumentation packages**

   ```bash
   composer require \
     open-telemetry/sdk \
     open-telemetry/exporter-otlp \
     open-telemetry/opentelemetry-auto-laravel \
     open-telemetry/opentelemetry-auto-psr18 \
     open-telemetry/opentelemetry-auto-io \
     php-http/guzzle7-adapter
   ```

3. **Install additional instrumentation packages (optional)**

   ```bash
   composer require \
     open-telemetry/opentelemetry-auto-pdo \
     open-telemetry/opentelemetry-auto-slim \
     monolog/monolog \
     predis/predis
   ```

## Configuration

### Environment Variables

**.env**

```bash
# Application
APP_NAME=Laravel-App
APP_ENV=production
APP_DEBUG=false

# OpenTelemetry Configuration
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel-app
OTEL_EXPORTER_OTLP_ENDPOINT=$last9_otlp_endpoint
OTEL_EXPORTER_OTLP_HEADERS="Authorization=$last9_otlp_auth_header"
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_PROPAGATORS=baggage,tracecontext
OTEL_TRACES_SAMPLER=always_on
OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.0.0"
OTEL_LOG_LEVEL=error

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_db
DB_USERNAME=user
DB_PASSWORD=password

# Redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

# Queue
QUEUE_CONNECTION=redis
```

**config/otel.php**

```php
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | OpenTelemetry Configuration
    |--------------------------------------------------------------------------
    |
    | Configuration settings for OpenTelemetry instrumentation
    |
    */

    'enabled' => env('OTEL_PHP_AUTOLOAD_ENABLED', true),

    'service_name' => env('OTEL_SERVICE_NAME', env('APP_NAME', 'laravel-app')),

    'exporter' => [
        'otlp' => [
            'endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT'),
            'headers' => env('OTEL_EXPORTER_OTLP_HEADERS'),
        ],
    ],

    'resource_attributes' => [
        'service.name' => env('OTEL_SERVICE_NAME', env('APP_NAME', 'laravel-app')),
        'service.version' => env('OTEL_SERVICE_VERSION', '1.0.0'),
        'deployment.environment' => env('APP_ENV', 'production'),
        'framework.name' => 'laravel',
        'framework.version' => app()->version(),
    ],

    'sampling' => [
        'traces' => [
            'sampler' => env('OTEL_TRACES_SAMPLER', 'always_on'),
            'probability' => env('OTEL_TRACES_SAMPLER_PROBABILITY', 1.0),
        ],
    ],

    'propagators' => explode(',', env('OTEL_PROPAGATORS', 'baggage,tracecontext')),

    'log_level' => env('OTEL_LOG_LEVEL', 'error'),
];
```

### Auto-Instrumentation Setup

**bootstrap/app.php**

```php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // Add OpenTelemetry middleware
        $middleware->web(append: [
            \App\Http\Middleware\OpenTelemetryMiddleware::class,
        ]);

        $middleware->api(append: [
            \App\Http\Middleware\OpenTelemetryMiddleware::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // Exception handling will be instrumented automatically
    })
    ->create();
```

**app/Providers/OpenTelemetryServiceProvider.php**

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\TracerInterface;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;

class OpenTelemetryServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(TracerInterface::class, function ($app) {
            return $this->createTracer();
        });

        $this->app->singleton('otel.instrumentation', function ($app) {
            return new CachedInstrumentation('laravel-app', '1.0.0');
        });
    }

    public function boot(): void
    {
        if (!config('otel.enabled', true)) {
            return;
        }

        $this->initializeOpenTelemetry();
    }

    private function createTracer(): TracerInterface
    {
        return Globals::tracerProvider()->getTracer(
            config('otel.service_name', 'laravel-app'),
            '1.0.0'
        );
    }

    private function initializeOpenTelemetry(): void
    {
        $resource = $this->createResource();
        $tracerProvider = $this->createTracerProvider($resource);

        $sdk = Sdk::builder()
            ->setTracerProvider($tracerProvider)
            ->setAutoShutdown(true)
            ->build();
    }

    private function createResource(): ResourceInfo
    {
        $attributes = config('otel.resource_attributes', []);

        return ResourceInfoFactory::create(
            Attributes::create($attributes),
            ResourceAttributes::SCHEMA_URL
        );
    }

    private function createTracerProvider(ResourceInfo $resource): TracerProvider
    {
        $exporter = $this->createExporter();

        $spanProcessor = new BatchSpanProcessor(
            $exporter,
            Globals::clockInterface(),
            512, // maxExportBatchSize
            5000, // scheduleDelayMillis
            30000, // exportTimeoutMillis
            2048 // maxQueueSize
        );

        return TracerProvider::builder()
            ->addSpanProcessor($spanProcessor)
            ->setResource($resource)
            ->setSampler(new ParentBased(new AlwaysOnSampler()))
            ->build();
    }

    private function createExporter(): SpanExporter
    {
        $endpoint = config('otel.exporter.otlp.endpoint');
        $headers = [];

        if ($authHeader = config('otel.exporter.otlp.headers')) {
            if (str_starts_with($authHeader, 'Authorization=')) {
                $headers['Authorization'] = substr($authHeader, 14);
            } else {
                // Parse other headers if needed
                parse_str(str_replace(';', '&', $authHeader), $headers);
            }
        }

        if ($endpoint) {
            $transport = (new OtlpHttpTransportFactory())->create(
                $endpoint . '/v1/traces',
                'application/json',
                $headers
            );

            return new SpanExporter($transport);
        }

        // Fallback to console exporter for development
        return new SpanExporter(
            (new StreamTransportFactory())->create(STDOUT, 'application/json')
        );
    }
}
```

## Custom Middleware and Services

### OpenTelemetry Middleware

**app/Http/Middleware/OpenTelemetryMiddleware.php**

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use OpenTelemetry\SemConv\TraceAttributes;
use Symfony\Component\HttpFoundation\Response;

class OpenTelemetryMiddleware
{
    private CachedInstrumentation $instrumentation;

    public function __construct()
    {
        $this->instrumentation = new CachedInstrumentation('laravel-middleware', '1.0.0');
    }

    public function handle(Request $request, Closure $next): Response
    {
        $tracer = $this->instrumentation->tracer();
        $spanName = $this->getSpanName($request);

        $span = $tracer->spanBuilder($spanName)
            ->setSpanKind(SpanKind::KIND_SERVER)
            ->setAttributes([
                TraceAttributes::HTTP_REQUEST_METHOD => $request->getMethod(),
                TraceAttributes::URL_FULL => $request->fullUrl(),
                TraceAttributes::HTTP_ROUTE => $request->route()?->uri() ?? 'unknown',
                TraceAttributes::USER_AGENT_ORIGINAL => $request->userAgent(),
                TraceAttributes::CLIENT_ADDRESS => $request->ip(),
                'laravel.route.name' => $request->route()?->getName(),
                'laravel.route.action' => $request->route()?->getActionName(),
                'request.body.size' => $request->server('CONTENT_LENGTH', 0),
            ])
            ->startSpan();

        $scope = $span->activate();
        $startTime = hrtime(true);

        try {
            // Store span in request for use in controllers
            $request->attributes->set('otel.span', $span);
            $request->attributes->set('otel.context', Context::getCurrent());

            $response = $next($request);

            // Record response attributes
            $duration = (hrtime(true) - $startTime) / 1_000_000; // Convert to milliseconds

            $span->setAttributes([
                TraceAttributes::HTTP_RESPONSE_STATUS_CODE => $response->getStatusCode(),
                'response.body.size' => strlen($response->getContent()),
                'http.request.duration_ms' => $duration,
            ]);

            // Set span status based on HTTP status code
            if ($response->getStatusCode() >= 400) {
                $span->setStatus(StatusCode::STATUS_ERROR, 'HTTP ' . $response->getStatusCode());
            } else {
                $span->setStatus(StatusCode::STATUS_OK);
            }

            // Log request completion
            Log::info('Request completed', [
                'method' => $request->getMethod(),
                'url' => $request->fullUrl(),
                'status' => $response->getStatusCode(),
                'duration_ms' => $duration,
                'trace_id' => $span->getContext()->getTraceId(),
                'span_id' => $span->getContext()->getSpanId(),
            ]);

            return $response;

        } catch (\Throwable $e) {
            $span->recordException($e);
            $span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());

            Log::error('Request failed', [
                'method' => $request->getMethod(),
                'url' => $request->fullUrl(),
                'error' => $e->getMessage(),
                'trace_id' => $span->getContext()->getTraceId(),
                'span_id' => $span->getContext()->getSpanId(),
            ]);

            throw $e;

        } finally {
            $span->end();
            $scope->detach();
        }
    }

    private function getSpanName(Request $request): string
    {
        $method = $request->getMethod();
        $route = $request->route()?->uri() ?? $request->getPathInfo();

        return "{$method} {$route}";
    }
}
```

**app/Services/TelemetryService.php**

```php
<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;

class TelemetryService
{
    private CachedInstrumentation $instrumentation;

    public function __construct()
    {
        $this->instrumentation = new CachedInstrumentation('laravel-service', '1.0.0');
    }

    public function traceOperation(string $operationName, callable $operation, array $attributes = []): mixed
    {
        $tracer = $this->instrumentation->tracer();

        $span = $tracer->spanBuilder($operationName)
            ->setSpanKind(SpanKind::KIND_INTERNAL)
            ->setAttributes($attributes)
            ->startSpan();

        $scope = $span->activate();

        try {
            $result = $operation($span);
            $span->setStatus(StatusCode::STATUS_OK);
            return $result;

        } catch (\Throwable $e) {
            $span->recordException($e);
            $span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());

            Log::error('Operation failed', [
                'operation' => $operationName,
                'error' => $e->getMessage(),
                'trace_id' => $span->getContext()->getTraceId(),
                'span_id' => $span->getContext()->getSpanId(),
            ]);

            throw $e;

        } finally {
            $span->end();
            $scope->detach();
        }
    }

    public function getCurrentSpan(): ?Span
    {
        return Span::getCurrent();
    }

    public function addAttributes(array $attributes): void
    {
        $span = $this->getCurrentSpan();
        if ($span) {
            $span->setAttributes($attributes);
        }
    }

    public function addEvent(string $name, array $attributes = []): void
    {
        $span = $this->getCurrentSpan();
        if ($span) {
            $span->addEvent($name, $attributes);
        }
    }

    public function recordException(\Throwable $exception): void
    {
        $span = $this->getCurrentSpan();
        if ($span) {
            $span->recordException($exception);
        }
    }
}
```

## Controller Examples

**app/Http/Controllers/UserController.php**

```php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Services\TelemetryService;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class UserController extends Controller
{
    public function __construct(
        private UserService $userService,
        private TelemetryService $telemetry
    ) {}

    public function index(Request $request): AnonymousResourceCollection
    {
        return $this->telemetry->traceOperation('user_controller.index', function (Span $span) use ($request) {
            $perPage = $request->get('per_page', 15);
            $search = $request->get('search');

            $span->setAttributes([
                'controller' => 'UserController',
                'action' => 'index',
                'per_page' => $perPage,
                'has_search' => !empty($search),
            ]);

            $users = $this->userService->getUsers($perPage, $search);

            $span->setAttributes([
                'users.count' => $users->count(),
                'users.total' => $users->total(),
            ]);

            return UserResource::collection($users);
        }, [
            'operation' => 'list_users',
            'controller' => 'UserController',
        ]);
    }

    public function store(StoreUserRequest $request): JsonResponse
    {
        return $this->telemetry->traceOperation('user_controller.store', function (Span $span) use ($request) {
            $validatedData = $request->validated();

            $span->setAttributes([
                'controller' => 'UserController',
                'action' => 'store',
                'user.email' => $validatedData['email'],
            ]);

            $user = $this->userService->createUser($validatedData);

            $span->setAttributes([
                'user.created' => true,
                'user.id' => $user->id,
            ]);

            return response()->json([
                'message' => 'User created successfully',
                'data' => new UserResource($user),
            ], 201);
        }, [
            'operation' => 'create_user',
            'controller' => 'UserController',
        ]);
    }

    public function show(User $user): JsonResponse
    {
        return $this->telemetry->traceOperation('user_controller.show', function (Span $span) use ($user) {
            $span->setAttributes([
                'controller' => 'UserController',
                'action' => 'show',
                'user.id' => $user->id,
                'user.email' => $user->email,
            ]);

            // Load relationships with tracing
            $userWithRelations = $this->userService->getUserWithDetails($user->id);

            $span->setAttributes([
                'user.has_profile' => $userWithRelations->profile !== null,
                'user.posts_count' => $userWithRelations->posts_count ?? 0,
            ]);

            return response()->json([
                'data' => new UserResource($userWithRelations),
            ]);
        }, [
            'operation' => 'show_user',
            'controller' => 'UserController',
        ]);
    }

    public function update(UpdateUserRequest $request, User $user): JsonResponse
    {
        return $this->telemetry->traceOperation('user_controller.update', function (Span $span) use ($request, $user) {
            $validatedData = $request->validated();

            $span->setAttributes([
                'controller' => 'UserController',
                'action' => 'update',
                'user.id' => $user->id,
                'user.email' => $user->email,
            ]);

            $updatedUser = $this->userService->updateUser($user, $validatedData);

            $span->setAttributes([
                'user.updated' => true,
                'fields_updated' => array_keys($validatedData),
            ]);

            return response()->json([
                'message' => 'User updated successfully',
                'data' => new UserResource($updatedUser),
            ]);
        }, [
            'operation' => 'update_user',
            'controller' => 'UserController',
        ]);
    }

    public function destroy(User $user): JsonResponse
    {
        return $this->telemetry->traceOperation('user_controller.destroy', function (Span $span) use ($user) {
            $span->setAttributes([
                'controller' => 'UserController',
                'action' => 'destroy',
                'user.id' => $user->id,
            ]);

            $this->userService->deleteUser($user);

            $span->setAttributes([
                'user.deleted' => true,
            ]);

            return response()->json([
                'message' => 'User deleted successfully',
            ]);
        }, [
            'operation' => 'delete_user',
            'controller' => 'UserController',
        ]);
    }
}
```

## Service Layer Integration

**app/Services/UserService.php**

```php
<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;

class UserService
{
    public function __construct(
        private TelemetryService $telemetry
    ) {}

    public function getUsers(int $perPage = 15, ?string $search = null): LengthAwarePaginator
    {
        return $this->telemetry->traceOperation('user_service.get_users', function (Span $span) use ($perPage, $search) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'getUsers',
                'per_page' => $perPage,
                'has_search' => !empty($search),
            ]);

            $query = User::query();

            if ($search) {
                $query->where(function ($q) use ($search) {
                    $q->where('name', 'like', "%{$search}%")
                      ->orWhere('email', 'like', "%{$search}%");
                });

                $span->setAttributes(['search_term' => $search]);
            }

            $users = $query->paginate($perPage);

            $span->setAttributes([
                'results.count' => $users->count(),
                'results.total' => $users->total(),
                'results.pages' => $users->lastPage(),
            ]);

            return $users;
        });
    }

    public function getUserWithDetails(int $userId): User
    {
        return $this->telemetry->traceOperation('user_service.get_user_with_details', function (Span $span) use ($userId) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'getUserWithDetails',
                'user.id' => $userId,
            ]);

            // Try cache first
            $cacheKey = "user_details_{$userId}";

            return Cache::remember($cacheKey, 300, function () use ($userId, $span) {
                $span->addEvent('cache_miss', ['cache_key' => "user_details_{$userId}"]);

                $user = User::with(['profile', 'posts' => function ($query) {
                    $query->latest()->take(5);
                }])
                ->withCount('posts')
                ->findOrFail($userId);

                $span->setAttributes([
                    'user.found' => true,
                    'user.has_profile' => $user->profile !== null,
                    'user.posts_count' => $user->posts_count,
                    'cache.stored' => true,
                ]);

                return $user;
            });
        });
    }

    public function createUser(array $data): User
    {
        return $this->telemetry->traceOperation('user_service.create_user', function (Span $span) use ($data) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'createUser',
                'user.email' => $data['email'],
            ]);

            return DB::transaction(function () use ($data, $span) {
                $span->addEvent('transaction_started');

                // Hash password
                if (isset($data['password'])) {
                    $data['password'] = Hash::make($data['password']);
                }

                // Create user
                $user = User::create($data);

                $span->setAttributes([
                    'user.created' => true,
                    'user.id' => $user->id,
                ]);

                // Send welcome email (async)
                $this->sendWelcomeEmail($user);

                // Clear relevant caches
                $this->clearUserCaches();

                $span->addEvent('transaction_completed');

                return $user;
            });
        });
    }

    public function updateUser(User $user, array $data): User
    {
        return $this->telemetry->traceOperation('user_service.update_user', function (Span $span) use ($user, $data) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'updateUser',
                'user.id' => $user->id,
                'fields_to_update' => array_keys($data),
            ]);

            return DB::transaction(function () use ($user, $data, $span) {
                $span->addEvent('transaction_started');

                // Hash password if provided
                if (isset($data['password'])) {
                    $data['password'] = Hash::make($data['password']);
                }

                $user->update($data);

                $span->setAttributes([
                    'user.updated' => true,
                ]);

                // Clear cache for this user
                $this->clearUserCache($user->id);

                $span->addEvent('transaction_completed');

                return $user->refresh();
            });
        });
    }

    public function deleteUser(User $user): bool
    {
        return $this->telemetry->traceOperation('user_service.delete_user', function (Span $span) use ($user) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'deleteUser',
                'user.id' => $user->id,
            ]);

            return DB::transaction(function () use ($user, $span) {
                $span->addEvent('transaction_started');

                // Soft delete or hard delete based on business logic
                $deleted = $user->delete();

                $span->setAttributes([
                    'user.deleted' => $deleted,
                    'deletion_type' => 'soft_delete',
                ]);

                // Clear cache
                $this->clearUserCache($user->id);

                $span->addEvent('transaction_completed');

                return $deleted;
            });
        });
    }

    private function sendWelcomeEmail(User $user): void
    {
        $this->telemetry->traceOperation('user_service.send_welcome_email', function (Span $span) use ($user) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'sendWelcomeEmail',
                'user.id' => $user->id,
                'user.email' => $user->email,
            ]);

            try {
                // Queue welcome email (this will be traced by Laravel's queue instrumentation)
                Mail::to($user->email)->queue(new \App\Mail\WelcomeEmail($user));

                $span->setAttributes([
                    'email.queued' => true,
                    'email.type' => 'welcome',
                ]);

            } catch (\Exception $e) {
                $span->recordException($e);
                $span->setStatus(StatusCode::STATUS_ERROR, 'Failed to queue welcome email');

                // Log but don't fail user creation
                Log::error('Failed to queue welcome email', [
                    'user_id' => $user->id,
                    'error' => $e->getMessage(),
                ]);
            }
        });
    }

    private function clearUserCache(int $userId): void
    {
        $this->telemetry->traceOperation('user_service.clear_user_cache', function (Span $span) use ($userId) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'clearUserCache',
                'user.id' => $userId,
            ]);

            $keys = [
                "user_details_{$userId}",
                "user_posts_{$userId}",
                "user_profile_{$userId}",
            ];

            foreach ($keys as $key) {
                Cache::forget($key);
            }

            $span->setAttributes([
                'cache.keys_cleared' => count($keys),
            ]);
        });
    }

    private function clearUserCaches(): void
    {
        $this->telemetry->traceOperation('user_service.clear_user_caches', function (Span $span) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'clearUserCaches',
            ]);

            // Clear user list cache
            Cache::tags(['users'])->flush();

            $span->setAttributes([
                'cache.tags_cleared' => ['users'],
            ]);
        });
    }
}
```

## Database Integration

Laravel's Eloquent ORM is automatically instrumented when you install the OpenTelemetry auto-instrumentation packages. However, you can add custom attributes:

**app/Models/User.php**

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use OpenTelemetry\API\Trace\Span;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    // Automatically add tracing attributes to model events
    protected static function booted(): void
    {
        static::created(function (User $user) {
            $span = Span::getCurrent();
            if ($span) {
                $span->addEvent('user.created', [
                    'user.id' => $user->id,
                    'user.email' => $user->email,
                ]);
            }
        });

        static::updated(function (User $user) {
            $span = Span::getCurrent();
            if ($span) {
                $span->addEvent('user.updated', [
                    'user.id' => $user->id,
                    'changes' => array_keys($user->getChanges()),
                ]);
            }
        });

        static::deleted(function (User $user) {
            $span = Span::getCurrent();
            if ($span) {
                $span->addEvent('user.deleted', [
                    'user.id' => $user->id,
                ]);
            }
        });
    }

    public function profile()
    {
        return $this->hasOne(UserProfile::class);
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
```

## Queue Integration

**app/Jobs/ProcessUserRegistration.php**

```php
<?php

namespace App\Jobs;

use App\Models\User;
use App\Services\TelemetryService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

class ProcessUserRegistration implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(
        private User $user
    ) {}

    public function handle(TelemetryService $telemetry): void
    {
        $telemetry->traceOperation('process_user_registration', function (Span $span) {
            $span->setAttributes([
                'job' => 'ProcessUserRegistration',
                'user.id' => $this->user->id,
                'user.email' => $this->user->email,
                'queue.name' => $this->queue ?? 'default',
            ]);

            // Send welcome email
            $telemetry->traceOperation('send_welcome_email', function (Span $emailSpan) {
                $emailSpan->setAttributes([
                    'email.type' => 'welcome',
                    'email.recipient' => $this->user->email,
                ]);

                Mail::to($this->user)->send(new \App\Mail\WelcomeEmail($this->user));

                $emailSpan->setAttributes([
                    'email.sent' => true,
                ]);
            });

            // Create user profile
            $telemetry->traceOperation('create_user_profile', function (Span $profileSpan) {
                $profile = $this->user->profile()->create([
                    'bio' => '',
                    'avatar' => null,
                ]);

                $profileSpan->setAttributes([
                    'profile.created' => true,
                    'profile.id' => $profile->id,
                ]);
            });

            $span->setAttributes([
                'registration.processed' => true,
            ]);
        }, [
            'job.class' => static::class,
            'job.queue' => $this->queue ?? 'default',
        ]);
    }

    public function failed(\Throwable $exception): void
    {
        $telemetry = app(TelemetryService::class);

        $telemetry->recordException($exception);
        $telemetry->addAttributes([
            'job.failed' => true,
            'job.class' => static::class,
            'user.id' => $this->user->id,
        ]);
    }
}
```

## Production Deployment

### Docker Configuration

**Dockerfile**

```dockerfile
FROM php:8.2-fpm-alpine

# Install system dependencies
RUN apk add --no-cache \
    curl \
    git \
    icu-dev \
    libzip-dev \
    oniguruma-dev \
    autoconf \
    g++ \
    make

# Install PHP extensions
RUN docker-php-ext-install \
    pdo_mysql \
    mbstring \
    zip \
    intl \
    opcache

# Install OpenTelemetry extension
RUN pecl install opentelemetry && docker-php-ext-enable opentelemetry

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set working directory
WORKDIR /var/www/html

# Copy composer files
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts

# Copy application files
COPY . .

# Set permissions
RUN chown -R www-data:www-data /var/www/html
RUN chmod -R 755 /var/www/html/storage

# Copy PHP configuration
COPY docker/php/php.ini /usr/local/etc/php/php.ini
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Generate optimized autoload
RUN composer dump-autoload --optimize

# Run Laravel optimizations
RUN php artisan config:cache && \
    php artisan route:cache && \
    php artisan view:cache

USER www-data

EXPOSE 9000

CMD ["php-fpm"]
```

**docker-compose.yml**

```yaml
version: "3.8"

services:
  app:
    build: .
    container_name: laravel-app
    environment:
      # OpenTelemetry
      - OTEL_PHP_AUTOLOAD_ENABLED=true
      - OTEL_SERVICE_NAME=laravel-app
      - OTEL_EXPORTER_OTLP_ENDPOINT=$last9_otlp_endpoint
      - OTEL_EXPORTER_OTLP_HEADERS=Authorization=$last9_otlp_auth_header
      - OTEL_TRACES_EXPORTER=otlp
      - OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.0.0

      # Laravel
      - APP_ENV=production
      - APP_DEBUG=false
      - DB_CONNECTION=mysql
      - DB_HOST=mysql
      - DB_DATABASE=laravel_db
      - DB_USERNAME=laravel_user
      - DB_PASSWORD=laravel_password
      - REDIS_HOST=redis
    volumes:
      - ./storage/logs:/var/www/html/storage/logs
    depends_on:
      - mysql
      - redis

  nginx:
    image: nginx:alpine
    container_name: laravel-nginx
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app

  mysql:
    image: mysql:8.0
    container_name: laravel-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: laravel_db
      MYSQL_USER: laravel_user
      MYSQL_PASSWORD: laravel_password
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    container_name: laravel-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:
```

### Kubernetes Deployment

**k8s-deployment.yaml**

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: laravel-app
  labels:
    app: laravel-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: laravel-app
  template:
    metadata:
      labels:
        app: laravel-app
    spec:
      containers:
        - name: laravel-app
          image: your-registry/laravel-app:latest
          ports:
            - containerPort: 9000
          env:
            - name: OTEL_PHP_AUTOLOAD_ENABLED
              value: "true"
            - name: OTEL_SERVICE_NAME
              value: "laravel-app"
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "$last9_otlp_endpoint"
            - name: OTEL_EXPORTER_OTLP_HEADERS
              value: "Authorization=$last9_otlp_auth_header"
            - name: OTEL_RESOURCE_ATTRIBUTES
              value: "deployment.environment=production,service.version=1.0.0,k8s.cluster.name=production"
            - name: APP_ENV
              value: "production"
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: laravel-secrets
                  key: db-host
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: laravel-secrets
                  key: db-password
          livenessProbe:
            tcpSocket:
              port: 9000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            tcpSocket:
              port: 9000
            initialDelaySeconds: 5
            periodSeconds: 5
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: laravel-app-service
spec:
  selector:
    app: laravel-app
  ports:
    - protocol: TCP
      port: 9000
      targetPort: 9000
```

## Testing the Integration

1. **Start your application**

   ```bash
   # Local development
   php artisan serve

   # Or with Docker
   docker-compose up
   ```

2. **Test the endpoints**

   ```bash
   # API endpoints
   curl http://localhost:8000/api/users
   curl http://localhost:8000/api/users/1

   # Create a user
   curl -X POST http://localhost:8000/api/users \
     -H "Content-Type: application/json" \
     -d '{"name": "Test User", "email": "test@example.com", "password": "password123"}'
   ```

3. **View telemetry in Last9**

   Check your Last9 dashboard for:

   - HTTP request traces with Laravel-specific attributes
   - Eloquent ORM database operation spans
   - Queue job execution traces
   - Cache operation spans
   - Custom business logic spans and events
