# Lumen

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

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

This guide shows you how to instrument your Lumen micro-framework application with OpenTelemetry and send traces, metrics, and logs to Last9.

## Prerequisites

- PHP 8.1 or later
- Lumen 9.x or Lumen 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-lumen \
     open-telemetry/opentelemetry-auto-psr18 \
     php-http/guzzle7-adapter
   ```

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

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

## Configuration

### Environment Variables

**.env**

```bash
# Application
APP_NAME=Lumen-API
APP_ENV=production
APP_DEBUG=false
APP_KEY=your-app-key-here

# OpenTelemetry Configuration
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=lumen-api
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=lumen_db
DB_USERNAME=user
DB_PASSWORD=password

# Cache
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

# Queue
QUEUE_CONNECTION=redis
```

**config/otel.php**

Create `config/otel.php` and register it in `bootstrap/app.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', 'lumen-api')),

    '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', 'lumen-api')),
        'service.version' => env('OTEL_SERVICE_VERSION', '1.0.0'),
        'deployment.environment' => env('APP_ENV', 'production'),
        'framework.name' => 'lumen',
        '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'),
];
```

### Bootstrap Configuration

**bootstrap/app.php**

```php
<?php

require_once __DIR__.'/../vendor/autoload.php';

(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
    dirname(__DIR__)
))->bootstrap();

date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework.
|
*/

$app = new Laravel\Lumen\Application(
    dirname(__DIR__)
);

$app->withFacades();
$app->withEloquent();

/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
*/

$app->configure('otel');
$app->configure('database');
$app->configure('cache');

/*
|--------------------------------------------------------------------------
| Register Container Bindings
|--------------------------------------------------------------------------
*/

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
*/

$app->register(App\Providers\OpenTelemetryServiceProvider::class);
$app->register(App\Providers\TelemetryServiceProvider::class);

/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
*/

$app->middleware([
    App\Http\Middleware\OpenTelemetryMiddleware::class,
]);

$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
    'telemetry' => App\Http\Middleware\TelemetryMiddleware::class,
]);

/*
|--------------------------------------------------------------------------
| Register Routes
|--------------------------------------------------------------------------
*/

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/web.php';
});

return $app;
```

**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()
    {
        $this->app->singleton(TracerInterface::class, function ($app) {
            return $this->createTracer();
        });

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

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

        $this->initializeOpenTelemetry();
    }

    private function createTracer(): TracerInterface
    {
        return Globals::tracerProvider()->getTracer(
            config('otel.service_name', 'lumen-api'),
            '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);
            }
        }

        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')
        );
    }
}
```

## Middleware Implementation

**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;

class OpenTelemetryMiddleware
{
    private CachedInstrumentation $instrumentation;

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

    public function handle(Request $request, Closure $next)
    {
        $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->getPathInfo(),
                TraceAttributes::USER_AGENT_ORIGINAL => $request->userAgent(),
                TraceAttributes::CLIENT_ADDRESS => $request->ip(),
                'lumen.version' => app()->version(),
                'request.body.size' => $request->server('CONTENT_LENGTH', 0),
                'request.query.count' => count($request->query()),
            ])
            ->startSpan();

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

        try {
            // Store span 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;

            $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();
        $path = $request->getPathInfo();

        // Normalize path for better span naming
        $normalizedPath = $this->normalizePath($path);

        return "{$method} {$normalizedPath}";
    }

    private function normalizePath(string $path): string
    {
        // Replace numeric IDs with placeholders
        $path = preg_replace('/\/\d+/', '/{id}', $path);

        // Replace UUIDs with placeholders
        $path = preg_replace('/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/', '/{uuid}', $path);

        return $path;
    }
}
```

**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('lumen-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/ApiController.php**

```php
<?php

namespace App\Http\Controllers;

use App\Services\TelemetryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Laravel\Lumen\Routing\Controller as BaseController;

class ApiController extends BaseController
{
    protected TelemetryService $telemetry;

    public function __construct(TelemetryService $telemetry)
    {
        $this->telemetry = $telemetry;
    }

    protected function successResponse($data, string $message = 'Success', int $status = 200): JsonResponse
    {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $data,
        ], $status);
    }

    protected function errorResponse(string $message = 'Error', int $status = 400, array $errors = []): JsonResponse
    {
        $response = [
            'success' => false,
            'message' => $message,
        ];

        if (!empty($errors)) {
            $response['errors'] = $errors;
        }

        return response()->json($response, $status);
    }
}
```

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

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use OpenTelemetry\API\Trace\Span;

class UserController extends ApiController
{
    private UserService $userService;

    public function __construct(TelemetryService $telemetry, UserService $userService)
    {
        parent::__construct($telemetry);
        $this->userService = $userService;
    }

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

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

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

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

            return $this->successResponse($users, 'Users retrieved successfully');
        }, [
            'operation' => 'list_users',
            'controller' => 'UserController',
        ]);
    }

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

            // Validate request
            $validator = Validator::make($request->all(), [
                'name' => 'required|string|max:255',
                'email' => 'required|email|unique:users,email',
                'password' => 'required|string|min:8',
            ]);

            if ($validator->fails()) {
                $span->setAttributes([
                    'validation.failed' => true,
                    'validation.errors' => array_keys($validator->errors()->toArray()),
                ]);

                return $this->errorResponse(
                    'Validation failed',
                    422,
                    $validator->errors()->toArray()
                );
            }

            $validatedData = $validator->validated();
            $span->setAttributes(['user.email' => $validatedData['email']]);

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

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

            return $this->successResponse($user, 'User created successfully', 201);
        }, [
            'operation' => 'create_user',
            'controller' => 'UserController',
        ]);
    }

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

            $user = $this->userService->getUserById($id);

            if (!$user) {
                $span->setAttributes(['user.found' => false]);
                return $this->errorResponse('User not found', 404);
            }

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

            return $this->successResponse($user, 'User retrieved successfully');
        }, [
            'operation' => 'show_user',
            'controller' => 'UserController',
        ]);
    }

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

            // Validate request
            $validator = Validator::make($request->all(), [
                'name' => 'sometimes|required|string|max:255',
                'email' => 'sometimes|required|email|unique:users,email,' . $id,
                'password' => 'sometimes|required|string|min:8',
            ]);

            if ($validator->fails()) {
                $span->setAttributes([
                    'validation.failed' => true,
                    'validation.errors' => array_keys($validator->errors()->toArray()),
                ]);

                return $this->errorResponse(
                    'Validation failed',
                    422,
                    $validator->errors()->toArray()
                );
            }

            $validatedData = $validator->validated();
            $span->setAttributes(['fields_to_update' => array_keys($validatedData)]);

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

            if (!$user) {
                $span->setAttributes(['user.found' => false]);
                return $this->errorResponse('User not found', 404);
            }

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

            return $this->successResponse($user, 'User updated successfully');
        }, [
            'operation' => 'update_user',
            'controller' => 'UserController',
        ]);
    }

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

            $deleted = $this->userService->deleteUser($id);

            if (!$deleted) {
                $span->setAttributes(['user.found' => false]);
                return $this->errorResponse('User not found', 404);
            }

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

            return $this->successResponse(null, '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\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use OpenTelemetry\API\Trace\Span;

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

    public function getUsers(int $page = 1, int $perPage = 15, ?string $search = null): array
    {
        return $this->telemetry->traceOperation('user_service.get_users', function (Span $span) use ($page, $perPage, $search) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'getUsers',
                'page' => $page,
                '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]);
            }

            $total = $query->count();
            $users = $query->skip(($page - 1) * $perPage)
                           ->take($perPage)
                           ->get()
                           ->toArray();

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

            return [
                'data' => $users,
                'total' => $total,
                'page' => $page,
                'per_page' => $perPage,
                'pages' => ceil($total / $perPage),
            ];
        });
    }

    public function getUserById(int $id): ?array
    {
        return $this->telemetry->traceOperation('user_service.get_user_by_id', function (Span $span) use ($id) {
            $span->setAttributes([
                'service' => 'UserService',
                'operation' => 'getUserById',
                'user.id' => $id,
            ]);

            // Try cache first
            $cacheKey = "user_{$id}";
            $user = Cache::get($cacheKey);

            if ($user) {
                $span->setAttributes(['cache.hit' => true]);
                return $user;
            }

            $span->setAttributes(['cache.hit' => false]);

            $user = User::find($id);

            if ($user) {
                $userData = $user->toArray();

                // Cache for 5 minutes
                Cache::put($cacheKey, $userData, 300);

                $span->setAttributes([
                    'user.found' => true,
                    'user.email' => $userData['email'],
                    'cache.stored' => true,
                ]);

                return $userData;
            }

            $span->setAttributes(['user.found' => false]);
            return null;
        });
    }

    public function createUser(array $data): array
    {
        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);
                $userData = $user->toArray();

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

                // Clear user list cache
                $this->clearUserListCache();

                $span->addEvent('transaction_completed');

                return $userData;
            });
        });
    }

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

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

                $user = User::find($id);

                if (!$user) {
                    $span->setAttributes(['user.found' => false]);
                    return null;
                }

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

                $user->update($data);
                $userData = $user->fresh()->toArray();

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

                // Clear caches
                $this->clearUserCache($id);
                $this->clearUserListCache();

                $span->addEvent('transaction_completed');

                return $userData;
            });
        });
    }

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

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

                $user = User::find($id);

                if (!$user) {
                    $span->setAttributes(['user.found' => false]);
                    return false;
                }

                $deleted = $user->delete();

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

                // Clear caches
                $this->clearUserCache($id);
                $this->clearUserListCache();

                $span->addEvent('transaction_completed');

                return $deleted;
            });
        });
    }

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

            Cache::forget("user_{$id}");

            $span->setAttributes(['cache.cleared' => true]);
        });
    }

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

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

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

## Routes Definition

**routes/web.php**

```php
<?php

/** @var \Laravel\Lumen\Routing\Router $router */

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
*/

$router->get('/', function () use ($router) {
    return response()->json([
        'message' => 'Welcome to Lumen API',
        'version' => $router->app->version(),
        'timestamp' => now()->toISOString(),
    ]);
});

// Health check
$router->get('/health', function () {
    return response()->json([
        'status' => 'healthy',
        'timestamp' => now()->toISOString(),
        'version' => app()->version(),
    ]);
});

// API Routes
$router->group(['prefix' => 'api/v1'], function () use ($router) {
    // Users
    $router->group(['prefix' => 'users'], function () use ($router) {
        $router->get('/', 'UserController@index');
        $router->post('/', 'UserController@store');
        $router->get('/{id:[0-9]+}', 'UserController@show');
        $router->put('/{id:[0-9]+}', 'UserController@update');
        $router->delete('/{id:[0-9]+}', 'UserController@destroy');
    });

    // Stats endpoint
    $router->get('/stats', function () {
        return response()->json([
            'users_count' => \App\Models\User::count(),
            'memory_usage' => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true),
        ]);
    });
});
```

## 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

USER www-data

EXPOSE 9000

CMD ["php-fpm"]
```

**docker-compose.yml**

```yaml
version: "3.8"

services:
  app:
    build: .
    container_name: lumen-api
    environment:
      # OpenTelemetry
      - OTEL_PHP_AUTOLOAD_ENABLED=true
      - OTEL_SERVICE_NAME=lumen-api
      - 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

      # Lumen
      - APP_ENV=production
      - APP_DEBUG=false
      - DB_CONNECTION=mysql
      - DB_HOST=mysql
      - DB_DATABASE=lumen_db
      - DB_USERNAME=lumen_user
      - DB_PASSWORD=lumen_password
      - CACHE_DRIVER=redis
      - REDIS_HOST=redis
    volumes:
      - ./storage/logs:/var/www/html/storage/logs
    depends_on:
      - mysql
      - redis

  nginx:
    image: nginx:alpine
    container_name: lumen-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: lumen-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: lumen_db
      MYSQL_USER: lumen_user
      MYSQL_PASSWORD: lumen_password
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    container_name: lumen-redis
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:
```

## Testing the Integration

1. **Start your application**

   ```bash
   # Local development
   php -S localhost:8000 -t public

   # Or with Docker
   docker-compose up
   ```

2. **Test the endpoints**

   ```bash
   # Health check
   curl http://localhost:8000/health

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

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

   # Get stats
   curl http://localhost:8000/api/v1/stats
   ```

3. **View telemetry in Last9**

   Check your Last9 dashboard for:

   - HTTP request traces with Lumen-specific attributes
   - Database operation spans with Eloquent ORM details
   - Cache operation traces
   - Custom business logic spans and events
