summaryrefslogtreecommitdiff
path: root/src/quote
diff options
context:
space:
mode:
Diffstat (limited to 'src/quote')
-rw-r--r--src/quote/.dockerignore4
-rw-r--r--src/quote/.gitignore7
-rw-r--r--src/quote/Dockerfile39
-rw-r--r--src/quote/README.md46
-rw-r--r--src/quote/app/dependencies.php30
-rw-r--r--src/quote/app/routes.php74
-rw-r--r--src/quote/app/settings.php30
-rw-r--r--src/quote/composer.json34
-rw-r--r--src/quote/public/index.php94
-rw-r--r--src/quote/src/Application/Settings/Settings.php27
-rw-r--r--src/quote/src/Application/Settings/SettingsInterface.php18
11 files changed, 403 insertions, 0 deletions
diff --git a/src/quote/.dockerignore b/src/quote/.dockerignore
new file mode 100644
index 0000000..98a3aae
--- /dev/null
+++ b/src/quote/.dockerignore
@@ -0,0 +1,4 @@
+.dockerignore
+.idea
+Dockerfile
+vendor
diff --git a/src/quote/.gitignore b/src/quote/.gitignore
new file mode 100644
index 0000000..63066a6
--- /dev/null
+++ b/src/quote/.gitignore
@@ -0,0 +1,7 @@
+.idea/
+.vscode/
+/coverage/
+/vendor/
+/logs/*
+!/logs/README.md
+.phpunit.result.cache
diff --git a/src/quote/Dockerfile b/src/quote/Dockerfile
new file mode 100644
index 0000000..9811889
--- /dev/null
+++ b/src/quote/Dockerfile
@@ -0,0 +1,39 @@
+# Copyright The OpenTelemetry Authors
+# SPDX-License-Identifier: Apache-2.0
+
+
+FROM ghcr.io/mlocati/php-extension-installer:2.7.34 AS installer
+
+FROM docker.io/library/composer:2.8.9 AS vendor
+
+WORKDIR /tmp/
+
+COPY ./src/quote/composer.json composer.json
+
+RUN composer install \
+ --ignore-platform-reqs \
+ --no-interaction \
+ --no-plugins \
+ --no-scripts \
+ --no-dev \
+ --prefer-dist
+
+FROM docker.io/library/php:8.4-cli-alpine3.22
+
+COPY --from=installer /usr/bin/install-php-extensions /usr/local/bin/
+
+RUN install-php-extensions opcache pcntl protobuf opentelemetry
+
+WORKDIR /var/www
+
+USER www-data
+
+COPY --from=vendor /tmp/vendor/ vendor/
+
+COPY ./src/quote/app/ app/
+COPY ./src/quote/public/ public/
+COPY ./src/quote/src/ src/
+
+EXPOSE ${QUOTE_PORT}
+
+CMD ["php", "public/index.php"]
diff --git a/src/quote/README.md b/src/quote/README.md
new file mode 100644
index 0000000..e1deaff
--- /dev/null
+++ b/src/quote/README.md
@@ -0,0 +1,46 @@
+# Quote Service
+
+The Quote Service calculates the shipping costs,
+based on the number of items to be shipped.
+
+It is a PHP based service, using a combination of automatic and manual instrumentation.
+
+## Docker Build
+
+To build the quote service, run the following from root directory
+of opentelemetry-demo
+
+```sh
+docker compose build quote
+```
+
+## Run the service
+
+Execute the below command to run the service.
+
+```sh
+docker compose up quote
+```
+
+In order to get traffic into the service you have to deploy
+the whole opentelemetry-demo.
+
+Please follow the root README to do so.
+
+## Development
+
+To build and run the quote service locally:
+
+```sh
+docker build src/quote --target base -t quote
+cd src/quote
+docker run --rm -it -v $(pwd):/var/www -e QUOTE_PORT=8999 -p "8999:8999" quote
+```
+
+Then, send some curl requests:
+
+```sh
+curl --location 'http://localhost:8999/getquote' \
+--header 'Content-Type: application/json' \
+--data '{"numberOfItems":3}'
+```
diff --git a/src/quote/app/dependencies.php b/src/quote/app/dependencies.php
new file mode 100644
index 0000000..5d9ee1d
--- /dev/null
+++ b/src/quote/app/dependencies.php
@@ -0,0 +1,30 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+declare(strict_types=1);
+
+use App\Application\Settings\SettingsInterface;
+use DI\ContainerBuilder;
+use OpenTelemetry\API\Globals;
+use OpenTelemetry\Contrib\Logs\Monolog\Handler;
+use Monolog\Logger;
+use Psr\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+return function (ContainerBuilder $containerBuilder) {
+ $containerBuilder->addDefinitions([
+ LoggerInterface::class => function (ContainerInterface $c) {
+ $settings = $c->get(SettingsInterface::class);
+ $loggerSettings = $settings->get('logger');
+ $handler = new Handler(
+ Globals::loggerProvider(),
+ LogLevel::INFO,
+ );
+ return new Logger($loggerSettings['name'], [$handler]);
+ },
+ ]);
+};
diff --git a/src/quote/app/routes.php b/src/quote/app/routes.php
new file mode 100644
index 0000000..2c2231a
--- /dev/null
+++ b/src/quote/app/routes.php
@@ -0,0 +1,74 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+use OpenTelemetry\API\Globals;
+use OpenTelemetry\API\Trace\Span;
+use OpenTelemetry\API\Trace\SpanKind;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Psr\Log\LoggerInterface;
+use Slim\App;
+
+function calculateQuote($jsonObject): float
+{
+ $quote = 0.0;
+ $childSpan = Globals::tracerProvider()->getTracer('manual-instrumentation')
+ ->spanBuilder('calculate-quote')
+ ->setSpanKind(SpanKind::KIND_INTERNAL)
+ ->startSpan();
+ $childSpan->addEvent('Calculating quote');
+
+ try {
+ if (!array_key_exists('numberOfItems', $jsonObject)) {
+ throw new \InvalidArgumentException('numberOfItems not provided');
+ }
+ $numberOfItems = intval($jsonObject['numberOfItems']);
+ $costPerItem = rand(400, 1000)/10;
+ $quote = round($costPerItem * $numberOfItems, 2);
+
+ $childSpan->setAttribute('app.quote.items.count', $numberOfItems);
+ $childSpan->setAttribute('app.quote.cost.total', $quote);
+
+ $childSpan->addEvent('Quote calculated, returning its value');
+
+ //manual metrics
+ static $counter;
+ $counter ??= Globals::meterProvider()
+ ->getMeter('quotes')
+ ->createCounter('quotes', 'quotes', 'number of quotes calculated');
+ $counter->add(1, ['number_of_items' => $numberOfItems]);
+ } catch (\Exception $exception) {
+ $childSpan->recordException($exception);
+ } finally {
+ $childSpan->end();
+ return $quote;
+ }
+}
+
+return function (App $app) {
+ $app->post('/getquote', function (Request $request, Response $response, LoggerInterface $logger) {
+ $span = Span::getCurrent();
+ $span->addEvent('Received get quote request, processing it');
+
+ $jsonObject = $request->getParsedBody();
+
+ $data = calculateQuote($jsonObject);
+
+ $payload = json_encode($data);
+ $response->getBody()->write($payload);
+
+ $span->addEvent('Quote processed, response sent back', [
+ 'app.quote.cost.total' => $data
+ ]);
+ //exported as an opentelemetry log (see dependencies.php)
+ $logger->info('Calculated quote', [
+ 'total' => $data,
+ ]);
+
+ return $response
+ ->withHeader('Content-Type', 'application/json');
+ });
+};
diff --git a/src/quote/app/settings.php b/src/quote/app/settings.php
new file mode 100644
index 0000000..dd4774e
--- /dev/null
+++ b/src/quote/app/settings.php
@@ -0,0 +1,30 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+declare(strict_types=1);
+
+use App\Application\Settings\Settings;
+use App\Application\Settings\SettingsInterface;
+use DI\ContainerBuilder;
+use Psr\Log\LogLevel;
+
+return function (ContainerBuilder $containerBuilder) {
+ // Global Settings Object
+ $containerBuilder->addDefinitions([
+ SettingsInterface::class => function () {
+ return new Settings([
+ 'displayErrorDetails' => true, // Should be set to false in production
+ 'logError' => false,
+ 'logErrorDetails' => false,
+ 'logger' => [
+ 'name' => 'slim-app',
+ 'path' => 'php://stdout',
+ 'level' => LogLevel::DEBUG,
+ ],
+ ]);
+ }
+ ]);
+};
diff --git a/src/quote/composer.json b/src/quote/composer.json
new file mode 100644
index 0000000..0a921a6
--- /dev/null
+++ b/src/quote/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "openteletry-demo/quote",
+ "description": "Quote Service part of OpenTelemetry Demo",
+ "license": "Apache-2.0",
+ "require": {
+ "php": ">= 8.3",
+ "ext-json": "*",
+ "ext-pcntl": "*",
+ "monolog/monolog": "3.9.0",
+ "open-telemetry/api": "1.6.0",
+ "open-telemetry/sdk": "1.8.0",
+ "open-telemetry/exporter-otlp": "1.3.2",
+ "open-telemetry/opentelemetry-auto-slim": "1.2.0",
+ "open-telemetry/detector-container": "1.1.0",
+ "open-telemetry/opentelemetry-logger-monolog": "1.1.0",
+ "guzzlehttp/guzzle": "7.10.0",
+ "php-di/php-di": "7.1.1",
+ "php-di/slim-bridge": "3.4.1",
+ "php-http/guzzle7-adapter": "1.1.0",
+ "react/http": "v1.11.0",
+ "slim/psr7": "1.7.1",
+ "slim/slim": "4.15.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "src/"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": false
+ }
+ }
+}
diff --git a/src/quote/public/index.php b/src/quote/public/index.php
new file mode 100644
index 0000000..fd02e5b
--- /dev/null
+++ b/src/quote/public/index.php
@@ -0,0 +1,94 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+declare(strict_types=1);
+
+use DI\Bridge\Slim\Bridge;
+use DI\ContainerBuilder;
+use OpenTelemetry\API\Globals;
+use OpenTelemetry\SDK\Common\Configuration\Configuration;
+use OpenTelemetry\SDK\Common\Configuration\Variables;
+use OpenTelemetry\SDK\Logs\LoggerProviderInterface;
+use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
+use OpenTelemetry\SDK\Trace\TracerProviderInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use React\EventLoop\Loop;
+use React\Http\HttpServer;
+use React\Socket\SocketServer;
+use Slim\Factory\AppFactory;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+// Instantiate PHP-DI ContainerBuilder
+$containerBuilder = new ContainerBuilder();
+
+// Set up settings
+$settings = require __DIR__ . '/../app/settings.php';
+$settings($containerBuilder);
+
+// Set up dependencies
+$dependencies = require __DIR__ . '/../app/dependencies.php';
+$dependencies($containerBuilder);
+
+// Build PHP-DI Container instance
+$container = $containerBuilder->build();
+
+// Instantiate the app
+AppFactory::setContainer($container);
+$app = Bridge::create($container);
+
+// Register middleware
+$app->addRoutingMiddleware();
+
+// Register routes
+$routes = require __DIR__ . '/../app/routes.php';
+$routes($app);
+
+// Add Body Parsing Middleware
+$app->addBodyParsingMiddleware();
+
+// Add Error Middleware
+$errorMiddleware = $app->addErrorMiddleware(true, true, true);
+Loop::get()->addSignal(SIGTERM, function() {
+ exit;
+});
+
+/* workaround for non-async batch processors */
+if (($tracerProvider = Globals::tracerProvider()) instanceof TracerProviderInterface) {
+ Loop::addPeriodicTimer(Configuration::getInt(Variables::OTEL_BSP_SCHEDULE_DELAY)/1000, function() use ($tracerProvider) {
+ $tracerProvider->forceFlush();
+ });
+}
+if (($loggerProvider = Globals::loggerProvider()) instanceof LoggerProviderInterface) {
+ Loop::addPeriodicTimer(Configuration::getInt(Variables::OTEL_BLRP_SCHEDULE_DELAY)/1000, function() use ($loggerProvider) {
+ $loggerProvider->forceFlush();
+ });
+}
+if (($meterProvider = Globals::meterProvider()) instanceof MeterProviderInterface) {
+ Loop::addPeriodicTimer(Configuration::getInt(Variables::OTEL_METRIC_EXPORT_INTERVAL)/1000, function() use ($meterProvider) {
+ $meterProvider->forceFlush();
+ });
+}
+
+$server = new HttpServer(function (ServerRequestInterface $request) use ($app) {
+ $response = $app->handle($request);
+ echo sprintf('[%s] "%s %s HTTP/%s" %d %d %s',
+ date('Y-m-d H:i:sP'),
+ $request->getMethod(),
+ $request->getUri()->getPath(),
+ $request->getProtocolVersion(),
+ $response->getStatusCode(),
+ $response->getBody()->getSize(),
+ PHP_EOL,
+ );
+
+ return $response;
+});
+$address = '0.0.0.0:' . getenv('QUOTE_PORT');
+$socket = new SocketServer($address);
+$server->listen($socket);
+
+echo "Listening on: {$address}" . PHP_EOL;
diff --git a/src/quote/src/Application/Settings/Settings.php b/src/quote/src/Application/Settings/Settings.php
new file mode 100644
index 0000000..b13fe4c
--- /dev/null
+++ b/src/quote/src/Application/Settings/Settings.php
@@ -0,0 +1,27 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+declare(strict_types=1);
+
+namespace App\Application\Settings;
+
+class Settings implements SettingsInterface
+{
+ private array $settings;
+
+ public function __construct(array $settings)
+ {
+ $this->settings = $settings;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function get(string $key = '')
+ {
+ return (empty($key)) ? $this->settings : $this->settings[$key];
+ }
+}
diff --git a/src/quote/src/Application/Settings/SettingsInterface.php b/src/quote/src/Application/Settings/SettingsInterface.php
new file mode 100644
index 0000000..0a1a586
--- /dev/null
+++ b/src/quote/src/Application/Settings/SettingsInterface.php
@@ -0,0 +1,18 @@
+<?php
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+
+
+declare(strict_types=1);
+
+namespace App\Application\Settings;
+
+interface SettingsInterface
+{
+ /**
+ * @param string $key
+ * @return mixed
+ */
+ public function get(string $key = '');
+}