Move logging configuration to be per-domain (#399)

* chore: per-domain logging

* fix: lint

* fix: revert cdn configuration disabling the access log

* feat: more granular controls for logging

* chore(cr): bump copyright year to 2022

* fix(cr): missing error_log level in the global config

* fix(cr): `is-changed` indicators

* chore(cr): newline at end of file + eslint enforcement

* fix(cr): rows alignment when checkbox applies

* fix(cr): don't use default computed values

* fix: lint

* chore: use new flag names to allow backward compatability

* chore: global `access_log` should always be `off`

* feat: migrate old logging to new

* feat: option to turn on access_log and error_log on redirects

* fix: update copyright year

* fix: missing translation

* fix(cr): migration from global `error_log` being empty

* fix(cr): missing `return`

* fix(cr): account for a `server` dictionary without `domain`

* fix(cr): migrate previous `access_log` and `error_log` paths using the previous behavior

* chore(cr): additional logging comment

* feat(cr): disable error_log per domain

* fix(logging): use default paths

* fix(logging): retain the user values for error_log when toggling the log on/off

* fix(bc): new params shouldn't be overridden
This commit is contained in:
Kobi Meirson 2022-11-14 17:37:44 +02:00 committed by GitHub
parent f44832ed7a
commit e2a95a5ed4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 406 additions and 77 deletions

View file

@ -30,6 +30,7 @@ module.exports = {
'vue/html-self-closing': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-component-names': 0,
'eol-last': ['error', 'always'],
},
globals: {
'describe': true,

View file

@ -33,13 +33,11 @@ export default (domains, global) => {
config['location = /favicon.ico'] = {
log_not_found: 'off',
};
if (global.logging.accessLog.computed) config['location = /favicon.ico'].access_log = 'off';
config['# robots.txt'] = '';
config['location = /robots.txt'] = {
log_not_found: 'off',
};
if (global.logging.accessLog.computed) config['location = /robots.txt'].access_log = 'off';
if (global.performance.disableHtmlCaching.computed) {
// Disable HTML caching for changes take effect in time
@ -48,7 +46,6 @@ export default (domains, global) => {
config[loc] = {
add_header: 'Cache-Control "no-cache"',
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
@ -61,7 +58,6 @@ export default (domains, global) => {
config[loc] = {
expires: global.performance.assetsExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
} else {
// Assets & media separately
@ -71,7 +67,6 @@ export default (domains, global) => {
config[loc] = {
expires: global.performance.assetsExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
if (global.performance.mediaExpiration.computed) {
@ -80,7 +75,6 @@ export default (domains, global) => {
config[loc] = {
expires: global.performance.mediaExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
}
@ -93,7 +87,6 @@ export default (domains, global) => {
add_header: 'Access-Control-Allow-Origin "*"',
expires: global.performance.svgExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
} else {
// SVG & fonts separately
@ -104,7 +97,6 @@ export default (domains, global) => {
add_header: 'Access-Control-Allow-Origin "*"',
expires: global.performance.svgExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
if (global.performance.fontsExpiration.computed) {
@ -114,7 +106,6 @@ export default (domains, global) => {
add_header: 'Access-Control-Allow-Origin "*"',
expires: global.performance.fontsExpiration.computed,
};
if (global.logging.accessLog.computed) config[loc].access_log = 'off';
}
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,6 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { errorLogPathDisabled } from '../../util/logging';
import sslProfiles from '../../util/ssl_profiles';
import websiteConf from './website.conf';
@ -107,8 +108,13 @@ export default (domains, global) => {
}
config.http.push(['# Logging', '']);
config.http.push(['access_log', (global.logging.accessLog.computed.trim() + (global.logging.cloudflare.computed ? ' cloudflare' : '')) || 'off']);
config.http.push(['error_log', global.logging.errorLog.computed.trim() || '/dev/null']);
config.http.push(['access_log', 'off']);
if (global.logging.errorLogEnabled.computed) {
config.http.push(['error_log', global.logging.errorLogPath.computed.trim() +
` ${global.logging.errorLogLevel.computed}`]);
} else {
config.http.push(['error_log', errorLogPathDisabled]);
}
if (global.security.limitReq.computed) {
config.http.push(['# Limits', '']);

View file

@ -25,8 +25,8 @@ THE SOFTWARE.
*/
import { getSslCertificate, getSslCertificateKey } from '../../util/get_ssl_certificate';
import { getAccessLogDomainPath, getErrorLogDomainPath } from '../../util/get_log_paths';
import { extensions, gzipTypes } from '../../util/types_extensions';
import { getDomainAccessLog, getDomainErrorLog } from '../../util/logging';
import commonHsts from '../../util/common_hsts';
import securityConf from './security.conf';
import pythonConf from './python_uwsgi.conf';
@ -127,6 +127,19 @@ const httpRedirectConfig = (domain, global, ipPortPairs, domainName, redirectDom
config.push(...httpListen(domain, global, ipPortPairs));
config.push(['server_name', domainName]);
// Logging
if (domain.logging.redirectAccessLog.computed || domain.logging.redirectErrorLog.computed) {
config.push(['# logging', '']);
if (domain.logging.redirectAccessLog.computed) {
config.push(['access_log', getDomainAccessLog(domain, global)]);
}
if (domain.logging.redirectErrorLog.computed) {
config.push(['error_log', getDomainErrorLog(domain)]);
}
}
if (domain.https.certType.computed === 'letsEncrypt') {
// Let's encrypt
@ -221,15 +234,14 @@ export default (domain, domains, global, ipPortPairs) => {
}
// Access log or error log for domain
if (domain.logging.accessLog.computed || domain.logging.errorLog.computed) {
if (domain.logging.accessLogEnabled.computed || domain.logging.errorLogEnabled.computed) {
serverConfig.push(['# logging', '']);
if (domain.logging.accessLog.computed)
serverConfig.push(['access_log',
getAccessLogDomainPath(domain, global) + (global.logging.cloudflare.computed ? ' cloudflare' : '')]);
if (domain.logging.accessLogEnabled.computed)
serverConfig.push(['access_log', getDomainAccessLog(domain, global)]);
if (domain.logging.errorLog.computed)
serverConfig.push(['error_log', getErrorLogDomainPath(domain, global)]);
if (domain.logging.errorLogEnabled.computed)
serverConfig.push(['error_log', getDomainErrorLog(domain)]);
}
// index.php
@ -411,7 +423,19 @@ export default (domain, domains, global, ipPortPairs) => {
// HTTPS
redirectConfig.push(...sslConfig(domain, global));
// Logging
if (domain.logging.redirectAccessLog.computed || domain.logging.redirectErrorLog.computed) {
redirectConfig.push(['# logging', '']);
if (domain.logging.redirectAccessLog.computed) {
redirectConfig.push(['access_log', getDomainAccessLog(domain, global)]);
}
if (domain.logging.redirectErrorLog.computed) {
redirectConfig.push(['error_log', getDomainErrorLog(domain)]);
}
}
redirectConfig.push(['return',
`301 http${domain.https.https.computed ? 's' : ''}://${domain.server.wwwSubdomain.computed ? 'www.' : ''}${domain.server.domain.computed}$request_uri`]);

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -27,4 +27,7 @@ THE SOFTWARE.
export default {
byDomain: 'der Domain',
enableForThisDomain: 'Für diese Domain aktivieren',
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} "Seite nicht gefunden" Error Logging in`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'Füge Cloudflare Anfrage-Header dem Standard Log-Format hinzu',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'by domain',
enableForThisDomain: `${common.enable} for this domain`,
arguments: 'arguments',
level: 'logging level',
forRedirects: 'for redirects',
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} file not found error logging in`,
logformat: 'log_format',
level: 'logging level',
enableCloudflare: 'add Cloudflare request headers to the default log format',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'por dominio',
enableForThisDomain: `${common.enable} para este dominio`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} el registro de error de archivo no encontrado`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'agregar cabecera de petición de Cloudflare en el formato por defecto del registro',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'par domaine',
enableForThisDomain: `${common.enable} pour ce domaine`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} les erreurs de fichiers introuvables lors de la journalisation`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'ajouter les en-têtes de requête CloudFlare au format de journal par défaut',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: '(ドメインごと)',
enableForThisDomain: `このドメインで${common.enable}`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `FILE NOT FOUND エラーのロギングを${common.enable}`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'デフォルトのログフォーマットに Cloudflare のリクエストヘッダを追加する',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'wg. domen',
enableForThisDomain: `${common.enable} dla tej domeny`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} logowanie błędów o nieznalezionych plikach`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'dodaj nagłówki żądań Cloudflare do domyślnego formatu dziennika ',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'por domínio',
enableForThisDomain: `${common.enable} para este domínio`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} erro de arquivo não encontrado ao fazer login`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'adicionar cabeçalhos de solicitação Cloudflare ao formato de log padrão',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: 'по домену',
enableForThisDomain: `${common.enable} для этого домена`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable} логирование ошибок для файлов, которые не были найдены при запросе`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: 'добавить Cloudflare хедеры запроса в дефолтный формат логов',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: '在此站点',
enableForThisDomain: `为此站点${common.enable}`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable}“文件未找到”错误日志:`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: '将Cloudflare请求头部添加到默认日志格式',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -29,4 +29,7 @@ import common from '../../common';
export default {
byDomain: '在此網域',
enableForThisDomain: `為此網域${common.enable}`,
arguments: 'arguments', // TODO: translate
level: 'logging level', // TODO: translate
forRedirects: 'for redirects', // TODO: translate
};

View file

@ -29,6 +29,7 @@ import common from '../../common';
export default {
enableFileNotFoundErrorLogging: `${common.enable}「找不到檔案」錯誤日誌:`,
logformat: 'log_format',
level: 'logging level', // TODO: translate
enableCloudflare: '將 Cloudflare 請求標頭加入預設日誌格式',
cfRay: 'CF-Ray',
cfConnectingIp: 'CF-Connecting-IP',

View file

@ -227,6 +227,7 @@ THE SOFTWARE.
import ExternalLink from 'do-vue/src/templates/external_link';
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
import { serverDomainDefault } from '../../util/defaults';
import PrettyCheck from '../inputs/checkbox';
import PrettyRadio from '../inputs/radio';
@ -269,7 +270,7 @@ THE SOFTWARE.
},
letsEncryptEmail: {
default: '',
computed: 'info@example.com', // No default value, but a default computed
computed: `info@${serverDomainDefault}`, // No default value, but a default computed
enabled: true,
},
sslCertificate: {

View file

@ -26,35 +26,124 @@ THE SOFTWARE.
<template>
<div>
<div class="field is-horizontal">
<div class="field-label">
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">access_log {{ $t('templates.domainSections.logging.byDomain') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${accessLogChanged ? ' is-changed' : ''}`">
<div :class="`control${accessLogEnabledChanged ? ' is-changed' : ''}`">
<div class="checkbox">
<PrettyCheck v-model="accessLog" class="p-default p-curve p-fill p-icon">
<PrettyCheck v-model="accessLogEnabled" class="p-default p-curve p-fill p-icon">
{{ $t('templates.domainSections.logging.enableForThisDomain') }}
</PrettyCheck>
</div>
</div>
<div v-if="$props.data.accessLogEnabled.computed" :class="`control field is-horizontal is-expanded${accessLogPathChanged ? ' is-changed' : ''}`">
<input
v-model="accessLogPath"
class="input"
type="text"
:placeholder="$props.data.accessLogPath.default"
/>
</div>
</div>
</div>
</div>
<div v-if="$props.data.accessLogEnabled.computed" class="field is-horizontal">
<div class="field-label">
<label class="label">access_log {{ $t('templates.domainSections.logging.arguments') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${accessLogParametersChanged ? ' is-changed' : ''}`">
<input
v-model="accessLogParameters"
class="input"
type="text"
:placeholder="$props.data.accessLogParameters.default"
/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">access_log {{ $t('templates.domainSections.logging.forRedirects') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${redirectAccessLogChanged ? ' is-changed' : ''}`">
<div class="checkbox">
<PrettyCheck v-model="redirectAccessLog" class="p-default p-curve p-fill p-icon">
{{ $t('common.enable') }}
</PrettyCheck>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">error_log {{ $t('templates.domainSections.logging.byDomain') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${errorLogChanged ? ' is-changed' : ''}`">
<div :class="`control${errorLogEnabledChanged ? ' is-changed' : ''}`">
<div class="checkbox">
<PrettyCheck v-model="errorLog" class="p-default p-curve p-fill p-icon">
<PrettyCheck v-model="errorLogEnabled" class="p-default p-curve p-fill p-icon">
{{ $t('templates.domainSections.logging.enableForThisDomain') }}
</PrettyCheck>
</div>
<div v-if="$props.data.errorLogEnabled.computed" :class="`control field is-horizontal is-expanded${errorLogPathChanged ? ' is-changed' : ''}`">
<input
v-model="errorLogPath"
class="input"
type="text"
:disabled="!errorLogPathEnabled"
:placeholder="$props.data.errorLogPath.default"
/>
</div>
</div>
</div>
</div>
</div>
<div v-if="$props.data.errorLogEnabled.computed" class="field is-horizontal">
<div class="field-label">
<label class="label">error_log {{ $t('templates.domainSections.logging.level') }}</label>
</div>
<div class="field-body">
<div class="field is-horizontal">
<div
v-for="value in $props.data.errorLogLevel.options"
:class="`control${errorLogLevelChanged && value === errorLogLevel ? ' is-changed' : ''}`"
>
<div class="radio">
<PrettyRadio v-model="errorLogLevel" :value="value" class="p-default p-round p-fill p-icon">
{{ value }}
</PrettyRadio>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">error_log {{ $t('templates.domainSections.logging.forRedirects') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${redirectErrorLogChanged ? ' is-changed' : ''}`">
<div class="checkbox">
<PrettyCheck v-model="redirectErrorLog" class="p-default p-curve p-fill p-icon">
{{ $t('common.enable') }}
</PrettyCheck>
</div>
</div>
</div>
</div>
@ -65,14 +154,41 @@ THE SOFTWARE.
<script>
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
import { accessLogPathDefault, accessLogParamsDefault, errorLogPathDefault, errorLogPathDisabled, errorLogLevelDefault, errorLogLevelOptions, errorLogLevelDisabled } from '../../util/logging';
import PrettyCheck from '../inputs/checkbox';
import PrettyRadio from '../inputs/radio';
const defaults = {
accessLog: {
accessLogEnabled: {
default: true,
enabled: true,
},
accessLogPath: {
default: accessLogPathDefault,
enabled: true,
},
accessLogParameters: {
default: accessLogParamsDefault,
enabled: true,
},
redirectAccessLog: {
default: false,
enabled: true,
},
errorLog: {
errorLogEnabled: {
default: true,
enabled: true,
},
errorLogPath: {
default: errorLogPathDefault,
enabled: true,
},
errorLogLevel: {
default: errorLogLevelDefault,
options: [errorLogLevelDisabled, ...errorLogLevelOptions],
enabled: true,
},
redirectErrorLog: {
default: false,
enabled: true,
},
@ -85,10 +201,26 @@ THE SOFTWARE.
delegated: delegatedFromDefaults(defaults), // Data the parent will present here
components: {
PrettyCheck,
PrettyRadio,
},
props: {
data: Object, // Data delegated back to us from parent
},
computed: computedFromDefaults(defaults, 'logging'), // Getters & setters for the delegated data
watch: {
'$props.data.errorLogLevel': {
handler(data) {
// disable `error_log` path selection if log level is set to `none`
if (data.computed === errorLogLevelDisabled) {
this.$props.data.errorLogPath.enabled = false;
this.$props.data.errorLogPath.computed = errorLogPathDisabled;
} else if (!this.$props.data.errorLogPath.enabled) {
this.$props.data.errorLogPath.enabled = true;
this.$props.data.errorLogPath.computed = this.$props.data.errorLogPath.value;
}
},
deep: true,
},
},
};
</script>

View file

@ -147,16 +147,17 @@ THE SOFTWARE.
<script>
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
import { serverDomainDefault } from '../../util/defaults';
import PrettyCheck from '../inputs/checkbox';
const defaults = {
domain: {
default: 'example.com',
default: serverDomainDefault,
enabled: true,
},
path: {
default: '',
computed: '/var/www/example.com', // No default value, but a default computed
computed: `/var/www/${serverDomainDefault}`, // No default value, but a default computed
enabled: true,
},
documentRoot: {

View file

@ -26,37 +26,46 @@ THE SOFTWARE.
<template>
<div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">access_log</label>
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">error_log</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${accessLogChanged ? ' is-changed' : ''}`">
<div :class="`control${errorLogEnabledChanged ? ' is-changed' : ''}`">
<div class="checkbox">
<PrettyCheck v-model="errorLogEnabled" class="p-default p-curve p-fill p-icon">
{{ $t('common.enable') }}
</PrettyCheck>
</div>
</div>
<div v-if="$props.data.errorLogEnabled.computed" :class="`control field is-horizontal is-expanded${errorLogPathChanged ? ' is-changed' : ''}`">
<input
v-model="accessLog"
v-model="errorLogPath"
class="input"
type="text"
:placeholder="$props.data.accessLog.default"
:placeholder="$props.data.errorLogPath.default"
/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div v-if="$props.data.errorLogEnabled.computed" class="field is-horizontal">
<div class="field-label">
<label class="label">error_log</label>
<label class="label">error_log {{ $t('templates.globalSections.logging.level') }}</label>
</div>
<div class="field-body">
<div class="field">
<div :class="`control${errorLogChanged ? ' is-changed' : ''}`">
<input
v-model="errorLog"
class="input"
type="text"
:placeholder="$props.data.errorLog.default"
/>
<div class="field is-horizontal">
<div
v-for="value in $props.data.errorLogLevel.options"
:class="`control${errorLogLevelChanged && value === errorLogLevel ? ' is-changed' : ''}`"
>
<div class="radio">
<PrettyRadio v-model="errorLogLevel" :value="value" class="p-default p-round p-fill p-icon">
{{ value }}
</PrettyRadio>
</div>
</div>
</div>
</div>
@ -157,15 +166,22 @@ THE SOFTWARE.
<script>
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
import { errorLogPathDefault, errorLogLevelDefault, errorLogLevelOptions } from '../../util/logging';
import PrettyCheck from '../inputs/checkbox';
import PrettyRadio from '../inputs/radio';
const defaults = {
accessLog: {
default: '/var/log/nginx/access.log',
errorLogEnabled: {
default: false,
enabled: true,
},
errorLog: {
default: '/var/log/nginx/error.log warn',
errorLogPath: {
default: errorLogPathDefault,
enabled: true,
},
errorLogLevel: {
default: errorLogLevelDefault,
options: errorLogLevelOptions,
enabled: true,
},
logNotFound: {
@ -217,6 +233,7 @@ THE SOFTWARE.
delegated: delegatedFromDefaults(defaults), // Data the parent will present here
components: {
PrettyCheck,
PrettyRadio,
},
props: {
data: Object, // Data delegated back to us from parent

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -24,10 +24,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
export const getAccessLogDomainPath = (domain, global) => {
return global.logging.accessLog.computed.replace(/([^/]+)\.log$/, `${domain.server.domain.computed}.$1.log`);
};
export const getErrorLogDomainPath = (domain, global) => {
return global.logging.errorLog.computed.replace(/([^/]+)\.log (.+)$/, `${domain.server.domain.computed}.$1.log $2`);
};
export const serverDomainDefault = 'example.com';

View file

@ -0,0 +1,55 @@
/*
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
https://github.com/digitalocean/nginxconfig.io/blob/master/LICENSE or https://mit-license.org/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
export const accessLogPathDefault = '/var/log/nginx/access.log';
export const accessLogParamsDefault = 'buffer=512k flush=1m';
export const errorLogPathDefault = '/var/log/nginx/error.log';
export const errorLogPathDisabled = '/dev/null';
export const errorLogLevelDefault = 'warn';
export const errorLogLevelOptions = Object.freeze(['debug', 'info', 'notice', 'warn', 'error', 'crit', 'alert', 'emerg']);
export const errorLogLevelDisabled = 'none';
export const getDomainAccessLog = (domain, global) => {
let path = domain.logging.accessLogPath.computed.trim();
if (!path) {
path = accessLogPathDefault;
}
return path +
(global.logging.cloudflare.computed ? ' cloudflare' : '') +
(domain.logging.accessLogParameters.computed.trim() ? ` ${domain.logging.accessLogParameters.computed.trim()}`: '');
};
export const getDomainErrorLog = (domain) => {
let path = domain.logging.errorLogPath.computed.trim();
if (!path) {
path = errorLogPathDefault;
}
const errorLogLevel = errorLogLevelOptions.includes(domain.logging.errorLogLevel.computed) ? ` ${domain.logging.errorLogLevel.computed}` : '';
return `${path}${errorLogLevel}`;
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2021 DigitalOcean
Copyright 2022 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -26,6 +26,71 @@ THE SOFTWARE.
import isObject from './is_object';
import deepMerge from './deep_merge';
import { accessLogPathDefault, accessLogParamsDefault, errorLogPathDefault, errorLogPathDisabled, errorLogLevelDefault } from './logging';
import { serverDomainDefault } from './defaults';
// Migrate old logging settings to new ones
const migrateLogging = data => {
if (Object.keys(data).length === 0) return;
const globalLogging = 'logging' in data.global && isObject(data.global.logging) ? data.global.logging : {};
// global access_log
const [globalAccessLogPath, ...globalAccessLogParameters] = (globalLogging.accessLog || accessLogPathDefault).split(' ');
const globalAccessLogEnabled =
!('accessLog' in globalLogging) || // accessLog was enabled by default and might not appear at all
(globalAccessLogPath !== '' && globalAccessLogPath !== 'off'); // *or* someone turned it off explicitly
// global error_log
const [globalErrorLogPath, ...globalErrorLogLevel] = (globalLogging.errorLog || `${errorLogPathDefault} ${errorLogLevelDefault}`).split(' ');
const globalErrorLogEnabled =
!('errorLog' in globalLogging) || // errorLog was enabled by default and might not appear at all
(globalErrorLogPath !== '' && globalErrorLogPath !== errorLogPathDisabled); // *or* someone turned it off explicitly
// set global access_log / error_log files for every domain UNLESS it was explicitly
// enabled for the domain
for (const key in data.domains) {
if (!Object.prototype.hasOwnProperty.call(data.domains, key)) continue;
const perDomainServer = {
domain: serverDomainDefault,
...('server' in data.domains[key] && isObject(data.domains[key].server) ? data.domains[key].server : {}),
};
const perDomainLogging = 'logging' in data.domains[key] && isObject(data.domains[key].logging) ? data.domains[key].logging : {};
// access_log
let accessLogEnabled = globalAccessLogEnabled,
accessLogPath = globalAccessLogPath;
const accessLogParameters = globalAccessLogParameters.join(' ') || accessLogParamsDefault;
const perDomainAccessLogEnabled = !!perDomainLogging.accessLog;
if (perDomainAccessLogEnabled) {
accessLogEnabled = true;
accessLogPath = accessLogPath.replace(/([^/]+)\.log$/, `${perDomainServer.domain}.$1.log`);
}
// error_log
let errorLogEnabled = globalErrorLogEnabled,
errorLogPath = globalErrorLogPath;
const errorLogLevel = globalErrorLogLevel.join(' ') || errorLogLevelDefault;
const perDomainErrorLogEnabled = !!perDomainLogging.errorLog;
if (perDomainErrorLogEnabled) {
errorLogEnabled = true;
errorLogPath = errorLogPath.replace(/([^/]+)\.log$/, `${perDomainServer.domain}.$1.log`);
}
data.domains[key].logging = {
accessLogEnabled,
accessLogPath,
accessLogParameters,
errorLogEnabled,
errorLogPath,
errorLogLevel,
...perDomainLogging,
};
}
};
// Handle converting the old query format to our new query params
export default data => {
@ -68,4 +133,6 @@ export default data => {
deepMerge(data.domains[key], mappedData);
}
}
migrateLogging(data);
};