Add log drain settings for New Relic,Highlight.io, and Axiom

This commit is contained in:
Andras Bacsai
2023-11-17 10:21:19 +01:00
parent 6c7e091e1b
commit 88c5d87084
6 changed files with 215 additions and 76 deletions

View File

@@ -10,11 +10,18 @@ class InstallLogDrain
use AsAction; use AsAction;
public function handle(Server $server, string $type) public function handle(Server $server, string $type)
{ {
if ($type === 'newrelic') { try {
if (!$server->settings->is_logdrain_newrelic_enabled) { if ($type === 'none') {
throw new \Exception('New Relic log drain is not enabled.'); $command = [
} "echo 'Stopping old Fluent Bit'",
$config = base64_encode(" "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] [SERVICE]
Flush 5 Flush 5
Daemon off Daemon off
@@ -40,18 +47,36 @@ class InstallLogDrain
# https://log-api.newrelic.com/log/v1 - US # https://log-api.newrelic.com/log/v1 - US
base_uri \${BASE_URI} base_uri \${BASE_URI}
"); ");
} else if ($type === 'highlight') { } else if ($type === 'highlight') {
if (!$server->settings->is_logdrain_highlight_enabled) { if (!$server->settings->is_logdrain_highlight_enabled) {
throw new \Exception('Highlight log drain is not enabled.'); throw new \Exception('Highlight log drain is not enabled.');
} }
$config = base64_encode(' $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] [SERVICE]
Flush 5 Flush 5
Daemon off Daemon off
Tag container_logs
[INPUT] [INPUT]
Name forward Name forward
tag ${HIGHLIGHT_PROJECT_ID}
Buffer_Chunk_Size 1M Buffer_Chunk_Size 1M
Buffer_Max_Size 6M Buffer_Max_Size 6M
[FILTER] [FILTER]
@@ -63,14 +88,24 @@ class InstallLogDrain
Match * Match *
Set server_name {$server->name} Set server_name {$server->name}
[OUTPUT] [OUTPUT]
Name forward Name http
Match * Match *
Host otel.highlight.io Host api.axiom.co
Port 24224 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: services:
coolify-log-drain: coolify-log-drain:
image: cr.fluentbit.io/fluent/fluent-bit:2.0 image: cr.fluentbit.io/fluent/fluent-bit:2.0
@@ -83,7 +118,7 @@ services:
ports: ports:
- 127.0.0.1:24224:24224 - 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. This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
Files: Files:
@@ -91,40 +126,48 @@ Files:
- `docker-compose.yml` - docker-compose file to run Fluent Bit - `docker-compose.yml` - docker-compose file to run Fluent Bit
- `.env` - environment variables for Fluent Bit - `.env` - environment variables for Fluent Bit
'); ');
$license_key = $server->settings->logdrain_newrelic_license_key; $license_key = $server->settings->logdrain_newrelic_license_key;
$base_uri = $server->settings->logdrain_newrelic_base_uri; $base_uri = $server->settings->logdrain_newrelic_base_uri;
$base_path = config('coolify.base_config_path'); $base_path = config('coolify.base_config_path');
$config_path = $base_path . '/log-drains'; $config_path = $base_path . '/log-drains';
$fluent_bit_config = $config_path . '/fluent-bit.conf'; $fluent_bit_config = $config_path . '/fluent-bit.conf';
$compose_path = $config_path . '/docker-compose.yml'; $compose_path = $config_path . '/docker-compose.yml';
$readme_path = $config_path . '/README.md'; $readme_path = $config_path . '/README.md';
$command = [ $command = [
"echo 'Saving configuration'", "echo 'Saving configuration'",
"mkdir -p $config_path", "mkdir -p $config_path",
"echo '{$config}' | base64 -d > $fluent_bit_config", "echo '{$config}' | base64 -d > $fluent_bit_config",
"echo '{$compose}' | base64 -d > $compose_path", "echo '{$compose}' | base64 -d > $compose_path",
"echo '{$readme}' | base64 -d > $readme_path", "echo '{$readme}' | base64 -d > $readme_path",
"rm $config_path/.env || true", "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') { if ($type === 'newrelic') {
$add_envs_command = [ $add_envs_command = [
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env", "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);
} }
} }

View File

@@ -15,32 +15,70 @@ class LogDrains extends Component
'server.settings.logdrain_newrelic_base_uri' => 'required|string', 'server.settings.logdrain_newrelic_base_uri' => 'required|string',
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean', 'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
'server.settings.logdrain_highlight_project_id' => 'required|string', '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(); $this->parameters = get_route_parameters();
try { try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first(); $server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($server)) {
return redirect()->route('server.all'); return redirect()->route('server.all');
} }
$this->server = $server;
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function configureLogDrain(string $type) { public function configureLogDrain()
{
try { 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('serverRefresh');
$this->emit('success', 'Log drain configured successfully.'); $this->emit('success', 'Log drain service started successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function instantSave(string $type) { public function instantSave(string $type)
$this->submit($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 { try {
$this->resetErrorBag(); $this->resetErrorBag();
if ($type === 'newrelic') { if ($type === 'newrelic') {
@@ -51,6 +89,7 @@ class LogDrains extends Component
]); ]);
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_highlight_enabled' => false, 'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false,
]); ]);
} else if ($type === 'highlight') { } else if ($type === 'highlight') {
$this->validate([ $this->validate([
@@ -59,12 +98,38 @@ class LogDrains extends Component
]); ]);
$this->server->settings->update([ $this->server->settings->update([
'is_logdrain_newrelic_enabled' => false, '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->server->settings->save();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
return true;
} catch (\Throwable $e) { } 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() public function render()

View File

@@ -843,7 +843,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'fluentd-address' => "tcp://127.0.0.1:24224", 'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true", 'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true", 'fluentd-sub-second-precision' => "true",
] ]
], ],
'healthcheck' => [ 'healthcheck' => [
@@ -1021,6 +1020,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
listen [::]:80; listen [::]:80;
server_name localhost; 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 / { location / {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;

View File

@@ -61,6 +61,8 @@ class Server extends BaseModel
public $casts = [ public $casts = [
'proxy' => SchemalessAttributes::class, 'proxy' => SchemalessAttributes::class,
'logdrain_axiom_api_key' => 'encrypted',
'logdrain_newrelic_license_key' => 'encrypted',
]; ];
protected $schemalessAttributes = [ protected $schemalessAttributes = [
'proxy', 'proxy',

View File

@@ -18,6 +18,11 @@ return new class extends Migration
$table->boolean('is_logdrain_highlight_enabled')->default(false); $table->boolean('is_logdrain_highlight_enabled')->default(false);
$table->string('logdrain_highlight_project_id')->nullable(); $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 @@ return new class extends Migration
$table->dropColumn('is_logdrain_highlight_enabled'); $table->dropColumn('is_logdrain_highlight_enabled');
$table->dropColumn('logdrain_highlight_project_id'); $table->dropColumn('logdrain_highlight_project_id');
$table->dropColumn('is_logdrain_axiom_enabled');
$table->dropColumn('logdrain_axiom_dataset_name');
$table->dropColumn('logdrain_axiom_api_key');
}); });
} }
}; };

View File

@@ -2,45 +2,62 @@
<x-server.navbar :server="$server" :parameters="$parameters" /> <x-server.navbar :server="$server" :parameters="$parameters" />
<h2>Log Drains</h2> <h2>Log Drains</h2>
<div class="pb-4">Sends resource logs to external services.</div> <div class="pb-4">Sends resource logs to external services.</div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4 pt-4">
<div class="p-4 border border-coolgray-500"> <div class="p-4 border border-coolgray-500">
<h3>New Relic</h3>
<div class="w-32">
<x-forms.checkbox instantSave='instantSave("newrelic")' id="server.settings.is_logdrain_newrelic_enabled" label="Enabled" />
</div>
<form wire:submit.prevent='submit("newrelic")' class="flex flex-col"> <form wire:submit.prevent='submit("newrelic")' class="flex flex-col">
<h3>New Relic</h3>
<div class="w-32">
<x-forms.checkbox instantSave='instantSave("newrelic")'
id="server.settings.is_logdrain_newrelic_enabled" label="Enabled" />
</div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row"> <div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" required id="server.settings.logdrain_newrelic_license_key" label="License Key" /> <x-forms.input type="password" required id="server.settings.logdrain_newrelic_license_key"
<x-forms.input required id="server.settings.logdrain_newrelic_base_uri" placeholder="https://log-api.eu.newrelic.com/log/v1" label="Endpoint (EU / US)" /> label="License Key" />
<x-forms.input required id="server.settings.logdrain_newrelic_base_uri"
placeholder="https://log-api.eu.newrelic.com/log/v1" label="Endpoint (EU / US)" />
</div> </div>
</div> </div>
<div class="flex justify-end gap-4 pt-6"> <div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
<x-forms.button wire:click="configureLogDrain('newrelic')">
Configure On Server
</x-forms.button>
</div> </div>
</form> </form>
<h3>Highlight.io</h3> <h3>Highlight.io</h3>
<div class="w-32"> <div class="w-32">
<x-forms.checkbox instantSave='instantSave("highlight")' id="server.settings.is_logdrain_highlight_enabled" label="Enabled" /> <x-forms.checkbox instantSave='instantSave("highlight")'
id="server.settings.is_logdrain_highlight_enabled" label="Enabled" />
</div> </div>
<form wire:submit.prevent='submit("highlight")' class="flex flex-col"> <form wire:submit.prevent='submit("highlight")' class="flex flex-col">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row"> <div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" required id="server.settings.logdrain_highlight_project_id" label="Project Id" /> <x-forms.input type="password" required id="server.settings.logdrain_highlight_project_id"
label="Project Id" />
</div> </div>
</div> </div>
<div class="flex justify-end gap-4 pt-6"> <div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
<x-forms.button wire:click="configureLogDrain('highlight')"> </div>
Configure On Server </form>
<h3>Axiom</h3>
<div class="w-32">
<x-forms.checkbox instantSave='instantSave("axiom")' id="server.settings.is_logdrain_axiom_enabled"
label="Enabled" />
</div>
<form wire:submit.prevent='submit("axiom")' class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input type="password" required id="server.settings.logdrain_axiom_api_key"
label="API Key" />
<x-forms.input required id="server.settings.logdrain_axiom_dataset_name" label="Dataset Name" />
</div>
</div>
<div class="flex justify-end gap-4 pt-6">
<x-forms.button type="submit">
Save
</x-forms.button> </x-forms.button>
</div> </div>
</form> </form>