diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php index 79acda8e7..e4e43dc1b 100644 --- a/app/Actions/Server/InstallLogDrain.php +++ b/app/Actions/Server/InstallLogDrain.php @@ -10,11 +10,18 @@ class InstallLogDrain use AsAction; public function handle(Server $server, string $type) { - if ($type === 'newrelic') { - if (!$server->settings->is_logdrain_newrelic_enabled) { - throw new \Exception('New Relic log drain is not enabled.'); - } - $config = base64_encode(" + try { + if ($type === 'none') { + $command = [ + "echo 'Stopping old Fluent Bit'", + "docker rm -f coolify-log-drain || true", + ]; + return instant_remote_process($command, $server); + } else if ($type === 'newrelic') { + if (!$server->settings->is_logdrain_newrelic_enabled) { + throw new \Exception('New Relic log drain is not enabled.'); + } + $config = base64_encode(" [SERVICE] Flush 5 Daemon off @@ -40,18 +47,36 @@ public function handle(Server $server, string $type) # https://log-api.newrelic.com/log/v1 - US base_uri \${BASE_URI} "); - } else if ($type === 'highlight') { - if (!$server->settings->is_logdrain_highlight_enabled) { - throw new \Exception('Highlight log drain is not enabled.'); - } - $config = base64_encode(' + } else if ($type === 'highlight') { + if (!$server->settings->is_logdrain_highlight_enabled) { + throw new \Exception('Highlight log drain is not enabled.'); + } + $config = base64_encode(" +[SERVICE] + Flush 1 + Daemon off + Log_Level debug +[INPUT] + Name forward + tag \${HIGHLIGHT_PROJECT_ID} + Buffer_Chunk_Size 1M + Buffer_Max_Size 6M +[OUTPUT] + Name forward + Match * + Host otel.highlight.io + Port 24224 +"); + } else if ($type === 'axiom') { + if (!$server->settings->is_logdrain_axiom_enabled) { + throw new \Exception('Axiom log drain is not enabled.'); + } + $config = base64_encode(" [SERVICE] Flush 5 Daemon off - Tag container_logs [INPUT] Name forward - tag ${HIGHLIGHT_PROJECT_ID} Buffer_Chunk_Size 1M Buffer_Max_Size 6M [FILTER] @@ -63,14 +88,24 @@ public function handle(Server $server, string $type) Match * Set server_name {$server->name} [OUTPUT] - Name forward - Match * - Host otel.highlight.io - Port 24224 -'); - } + Name http + Match * + Host api.axiom.co + Port 443 + URI /v1/datasets/\${AXIOM_DATASET_NAME}/ingest + # Authorization Bearer should be an API token + Header Authorization Bearer \${AXIOM_API_KEY} + compress gzip + format json + json_date_key _time + json_date_format iso8601 + tls On +"); + } else { + throw new \Exception('Unknown log drain type.'); + } - $compose = base64_encode(" + $compose = base64_encode(" services: coolify-log-drain: image: cr.fluentbit.io/fluent/fluent-bit:2.0 @@ -83,7 +118,7 @@ public function handle(Server $server, string $type) ports: - 127.0.0.1:24224:24224 "); - $readme = base64_encode('# New Relic Log Drain + $readme = base64_encode('# New Relic Log Drain This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder. Files: @@ -91,40 +126,48 @@ public function handle(Server $server, string $type) - `docker-compose.yml` - docker-compose file to run Fluent Bit - `.env` - environment variables for Fluent Bit '); - $license_key = $server->settings->logdrain_newrelic_license_key; - $base_uri = $server->settings->logdrain_newrelic_base_uri; - $base_path = config('coolify.base_config_path'); + $license_key = $server->settings->logdrain_newrelic_license_key; + $base_uri = $server->settings->logdrain_newrelic_base_uri; + $base_path = config('coolify.base_config_path'); - $config_path = $base_path . '/log-drains'; - $fluent_bit_config = $config_path . '/fluent-bit.conf'; - $compose_path = $config_path . '/docker-compose.yml'; - $readme_path = $config_path . '/README.md'; - $command = [ - "echo 'Saving configuration'", - "mkdir -p $config_path", - "echo '{$config}' | base64 -d > $fluent_bit_config", - "echo '{$compose}' | base64 -d > $compose_path", - "echo '{$readme}' | base64 -d > $readme_path", - "rm $config_path/.env || true", + $config_path = $base_path . '/log-drains'; + $fluent_bit_config = $config_path . '/fluent-bit.conf'; + $compose_path = $config_path . '/docker-compose.yml'; + $readme_path = $config_path . '/README.md'; + $command = [ + "echo 'Saving configuration'", + "mkdir -p $config_path", + "echo '{$config}' | base64 -d > $fluent_bit_config", + "echo '{$compose}' | base64 -d > $compose_path", + "echo '{$readme}' | base64 -d > $readme_path", + "test -f $config_path/.env && rm $config_path/.env", - ]; - if ($type === 'newrelic') { - $add_envs_command = [ - "echo LICENSE_KEY=$license_key >> $config_path/.env", - "echo BASE_URI=$base_uri >> $config_path/.env", ]; - } else if ($type === 'highlight') { - $add_envs_command = [ - "echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", + if ($type === 'newrelic') { + $add_envs_command = [ + "echo LICENSE_KEY=$license_key >> $config_path/.env", + "echo BASE_URI=$base_uri >> $config_path/.env", + ]; + } else if ($type === 'highlight') { + $add_envs_command = [ + "echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", + ]; + } else if ($type === 'axiom') { + $add_envs_command = [ + "echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env", + "echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env", + ]; + } + $restart_command = [ + "echo 'Stopping old Fluent Bit'", + "cd $config_path && docker rm -f coolify-log-drain || true", + "echo 'Starting Fluent Bit'", + "cd $config_path && docker compose up -d --remove-orphans", ]; + $command = array_merge($command, $add_envs_command, $restart_command); + return instant_remote_process($command, $server); + } catch (\Throwable $e) { + return handleError($e); } - $restart_command = [ - "echo 'Stopping old Fluent Bit'", - "cd $config_path && docker rm -f coolify-log-drain || true", - "echo 'Starting Fluent Bit'", - "cd $config_path && docker compose up -d --remove-orphans", - ]; - $command = array_merge($command, $add_envs_command, $restart_command); - return instant_remote_process($command, $server); } } diff --git a/app/Http/Livewire/Server/LogDrains.php b/app/Http/Livewire/Server/LogDrains.php index 823886b3f..4545e205f 100644 --- a/app/Http/Livewire/Server/LogDrains.php +++ b/app/Http/Livewire/Server/LogDrains.php @@ -15,32 +15,70 @@ class LogDrains extends Component 'server.settings.logdrain_newrelic_base_uri' => 'required|string', 'server.settings.is_logdrain_highlight_enabled' => 'required|boolean', 'server.settings.logdrain_highlight_project_id' => 'required|string', + 'server.settings.is_logdrain_axiom_enabled' => 'required|boolean', + 'server.settings.logdrain_axiom_dataset_name' => 'required|string', + 'server.settings.logdrain_axiom_api_key' => 'required|string', + ]; + protected $validationAttributes = [ + 'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain', + 'server.settings.logdrain_newrelic_license_key' => 'New Relic license key', + 'server.settings.logdrain_newrelic_base_uri' => 'New Relic base URI', + 'server.settings.is_logdrain_highlight_enabled' => 'Highlight log drain', + 'server.settings.logdrain_highlight_project_id' => 'Highlight project ID', + 'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain', + 'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name', + 'server.settings.logdrain_axiom_api_key' => 'Axiom API key', ]; - public function mount() { + public function mount() + { $this->parameters = get_route_parameters(); try { - $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); - if (is_null($this->server)) { + $server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); + if (is_null($server)) { return redirect()->route('server.all'); } + $this->server = $server; } catch (\Throwable $e) { return handleError($e, $this); } } - public function configureLogDrain(string $type) { + public function configureLogDrain() + { try { - $this->server->logDrain($type); + if ($this->server->settings->is_logdrain_newrelic_enabled) { + $this->server->logDrain('newrelic'); + } else if ($this->server->settings->is_logdrain_highlight_enabled) { + $this->server->logDrain('highlight'); + } else if ($this->server->settings->is_logdrain_axiom_enabled) { + $this->server->logDrain('axiom'); + } else { + $this->server->logDrain('none'); + $this->emit('serverRefresh'); + $this->emit('success', 'Log drain service stopped.'); + return; + } $this->emit('serverRefresh'); - $this->emit('success', 'Log drain configured successfully.'); + $this->emit('success', 'Log drain service started successfully.'); } catch (\Throwable $e) { return handleError($e, $this); } } - public function instantSave(string $type) { - $this->submit($type); + public function instantSave(string $type) + { + try { + $ok = $this->submit($type); + ray($ok); + if (!$ok) { + return; + } + $this->configureLogDrain(); + } catch (\Throwable $e) { + return handleError($e, $this); + } } - public function submit(string $type) { + public function submit(string $type) + { try { $this->resetErrorBag(); if ($type === 'newrelic') { @@ -51,6 +89,7 @@ public function submit(string $type) { ]); $this->server->settings->update([ 'is_logdrain_highlight_enabled' => false, + 'is_logdrain_axiom_enabled' => false, ]); } else if ($type === 'highlight') { $this->validate([ @@ -59,12 +98,38 @@ public function submit(string $type) { ]); $this->server->settings->update([ 'is_logdrain_newrelic_enabled' => false, + 'is_logdrain_axiom_enabled' => false, + ]); + } else if ($type === 'axiom') { + $this->validate([ + 'server.settings.is_logdrain_axiom_enabled' => 'required|boolean', + 'server.settings.logdrain_axiom_dataset_name' => 'required|string', + 'server.settings.logdrain_axiom_api_key' => 'required|string', + ]); + $this->server->settings->update([ + 'is_logdrain_newrelic_enabled' => false, + 'is_logdrain_highlight_enabled' => false, ]); } $this->server->settings->save(); $this->emit('success', 'Settings saved successfully.'); + return true; } catch (\Throwable $e) { - return handleError($e, $this); + if ($type === 'newrelic') { + $this->server->settings->update([ + 'is_logdrain_newrelic_enabled' => false, + ]); + } else if ($type === 'highlight') { + $this->server->settings->update([ + 'is_logdrain_highlight_enabled' => false, + ]); + } else if ($type === 'axiom') { + $this->server->settings->update([ + 'is_logdrain_axiom_enabled' => false, + ]); + } + handleError($e, $this); + return false; } } public function render() diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index c74c973f9..e67a43e46 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -843,7 +843,6 @@ private function generate_compose_file() 'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-async' => "true", 'fluentd-sub-second-precision' => "true", - ] ], 'healthcheck' => [ @@ -1021,6 +1020,10 @@ private function build_image() listen [::]:80; server_name localhost; + // real_ip_header X-Forwarded-For; + // proxy_set_header X-Real-IP \$remote_addr; + // proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + location / { root /usr/share/nginx/html; index index.html; diff --git a/app/Models/Server.php b/app/Models/Server.php index 6fa5bda7b..83df8d61a 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -61,6 +61,8 @@ protected static function booted() public $casts = [ 'proxy' => SchemalessAttributes::class, + 'logdrain_axiom_api_key' => 'encrypted', + 'logdrain_newrelic_license_key' => 'encrypted', ]; protected $schemalessAttributes = [ 'proxy', diff --git a/database/migrations/2023_11_16_220647_add_log_drains.php b/database/migrations/2023_11_16_220647_add_log_drains.php index 82274a999..05b1ed054 100644 --- a/database/migrations/2023_11_16_220647_add_log_drains.php +++ b/database/migrations/2023_11_16_220647_add_log_drains.php @@ -18,6 +18,11 @@ public function up(): void $table->boolean('is_logdrain_highlight_enabled')->default(false); $table->string('logdrain_highlight_project_id')->nullable(); + + $table->boolean('is_logdrain_axiom_enabled')->default(false); + $table->string('logdrain_axiom_dataset_name')->nullable(); + $table->string('logdrain_axiom_api_key')->nullable(); + }); } @@ -33,6 +38,10 @@ public function down(): void $table->dropColumn('is_logdrain_highlight_enabled'); $table->dropColumn('logdrain_highlight_project_id'); + + $table->dropColumn('is_logdrain_axiom_enabled'); + $table->dropColumn('logdrain_axiom_dataset_name'); + $table->dropColumn('logdrain_axiom_api_key'); }); } }; diff --git a/resources/views/livewire/server/log-drains.blade.php b/resources/views/livewire/server/log-drains.blade.php index 38c5b0cc4..6a2a48349 100644 --- a/resources/views/livewire/server/log-drains.blade.php +++ b/resources/views/livewire/server/log-drains.blade.php @@ -2,45 +2,62 @@

Log Drains

Sends resource logs to external services.
-
+
-

New Relic

-
- -
+

New Relic

+
+ +
- - + +
-
Save - - Configure On Server -

Highlight.io

- +
- +
Save - - Configure On Server +
+
+

Axiom

+
+ +
+
+
+
+ + +
+
+
+ + Save