diff --git a/config/default.ts b/config/default.ts index 7416fc84..98b5b2a4 100644 --- a/config/default.ts +++ b/config/default.ts @@ -18,6 +18,7 @@ import {ConfigFile} from '../src/common'; const config: ConfigFile = { activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'], hiddenRoutes: ['/bulk'], + logFormat: 'default', metrics: false, outdatedVersions: ['0\\.8\\.\\d+', '0\\.5\\.\\d+', '0\\.6\\.\\d+', '0\\.7\\.\\d+'], output: '/etc/nginx/http.d/default.conf', diff --git a/fixtures/logFormatters.template b/fixtures/logFormatters.template new file mode 100644 index 00000000..d497f76a --- /dev/null +++ b/fixtures/logFormatters.template @@ -0,0 +1,32 @@ +map $time_iso8601 $time_iso8601_dateTime { + ~([^+]+) $1; +} +map $time_iso8601 $time_iso8601_TZ { + ~\+([0-9:]+)$ $1; +} +map $msec $millisec { + ~\.([0-9]+)$ $1; +} + +log_format json escape=json '{ "nginx_timestamp": "$time_iso8601_dateTime.$millisec+$time_iso8601_TZ", ' + '"remote_addr": "$remote_addr", ' + '"connection": "$connection", ' + '"connection_requests": $connection_requests, ' + '"pipe": "$pipe", ' + '"body_bytes_sent": $body_bytes_sent, ' + '"request_length": $request_length, ' + '"request_time": $request_time, ' + '"response_status": $status, ' + '"request": "$request", ' + '"request_method": "$request_method", ' + '"host": "$host", ' + '"upstream_cache_status": "$upstream_cache_status", ' + '"upstream_addr": "$upstream_addr", ' + '"http_x_forwarded_for": "$http_x_forwarded_for", ' + '"http_referrer": "$http_referer", ' + '"http_user_agent": "$http_user_agent", ' + '"http_version": "$server_protocol", ' + '"remote_user": "$remote_user", ' + '"http_x_forwarded_proto": "$http_x_forwarded_proto", ' + '"upstream_response_time": "$upstream_response_time", ' + '"nginx_access": true }'; diff --git a/fixtures/metrics.template b/fixtures/metrics.template index 2efab471..7af1dc8b 100644 --- a/fixtures/metrics.template +++ b/fixtures/metrics.template @@ -5,9 +5,10 @@ map $status $omitOKs { server { listen 8080; - access_log /dev/stdout combined if=$omitOKs; + error_log stderr; + access_log /dev/stdout {{{ logFormat }}} if=$omitOKs; location /metrics { - vhost_traffic_status_display; - vhost_traffic_status_display_format prometheus; + vhost_traffic_status_display; + vhost_traffic_status_display_format prometheus; } } diff --git a/nginx.conf.template b/nginx.conf.template index 34f570ee..6cadc1f4 100644 --- a/nginx.conf.template +++ b/nginx.conf.template @@ -1,3 +1,5 @@ +{{{ logFormatters }}} + {{{ metrics }}} {{{ dockerVersionMap }}} @@ -19,6 +21,9 @@ map $isRateLimited $rateLimit { limit_req_zone $rateLimit zone=customstappslimit:10m rate=20r/s; server { + error_log stderr; + access_log /dev/stdout {{{ logFormat }}}; + {{{ listener }}} {{{ visibleRoutes }}} diff --git a/src/common.ts b/src/common.ts index 1a6e9f03..e5b7f3c7 100644 --- a/src/common.ts +++ b/src/common.ts @@ -47,6 +47,20 @@ export interface SSLFilePaths { dhparam: string; } +/** + * Supported log formats for config + */ +type SupportedLogFormatsKeys = 'default' | 'combined' | 'json'; + +/** + * Map supported formats to stings used in template view + */ +export const SupportedLogFormats: {[key in SupportedLogFormatsKeys]: string} = { + default: 'combined', + combined: 'combined', + json: 'json', +}; + /** * A representation of the config file */ @@ -59,10 +73,14 @@ export interface ConfigFile { * List of hidden routes */ hiddenRoutes: string[]; + /** + * Sets log format (default or json) + */ + logFormat: SupportedLogFormatsKeys; /** * Enables metrics on /metrics route */ - metrics: boolean; + metrics?: boolean; /** * List of outdated versions */ @@ -97,6 +115,14 @@ export interface TemplateView { * Listener */ listener: string; + /** + * Log format to use + */ + logFormat: string; + /** + * Custom Log formatters + */ + logFormatters: string; /** * Local server with listener for /metrics route */ diff --git a/src/main.ts b/src/main.ts index 0ece216d..88291099 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,6 +26,7 @@ import { protocolHardeningParameters, SSLFilePaths, sslHardeningParameters, + SupportedLogFormats, TemplateView, } from './common'; @@ -244,11 +245,14 @@ ${protocolHardeningParameters} /** * Reads predefined server entry with metrics location */ -export async function generateMetricsServer(enableMetrics: boolean): Promise { +export async function generateMetricsServer(logFormat: string, enableMetrics?: boolean): Promise { if (!enableMetrics) { return ''; } - return asyncReadFile('./fixtures/metrics.template', 'utf8'); + + return renderTemplate(path.join('fixtures', 'metrics.template'), { + logFormat: logFormat, + }); } /** @@ -302,6 +306,13 @@ export async function getTemplateView(containers: Dockerode.ContainerInfo[]): Pr }); }); + const logFormattingPromise = renderTemplate(path.join('fixtures', 'logFormatters.template'), {}); + + const logFormat = + configFile.logFormat in SupportedLogFormats + ? SupportedLogFormats[configFile.logFormat] + : SupportedLogFormats.default; + return { dockerVersionMap: await generateUpstreamMap( configFile.activeVersions, @@ -310,7 +321,9 @@ export async function getTemplateView(containers: Dockerode.ContainerInfo[]): Pr ), hiddenRoutes: (await Promise.all(hiddenRoutesPromises)).join(''), listener: generateListener(configFile.sslFilePaths), - metrics: await generateMetricsServer(configFile.metrics), + logFormat: logFormat, + logFormatters: await logFormattingPromise, + metrics: await generateMetricsServer(logFormat, configFile.metrics), rateLimitAllowList: generateRateLimitAllowList(configFile.rateLimitAllowList), staticRoute: await renderTemplate(path.join('fixtures', 'staticRoute.template'), {cors}), visibleRoutes: (await Promise.all(visibleRoutesPromises)).join(''), diff --git a/test/main.spec.ts b/test/main.spec.ts index 65ff6b07..e0dc308b 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -402,12 +402,12 @@ Please check if docker is running and Node.js can access the docker socket (/var @test async 'include metrics config'() { - expect(await generateMetricsServer(true)).length.to.be.greaterThan(1); + expect(await generateMetricsServer('test', true)).length.to.be.greaterThan(1); } @test async 'omit metrics config'() { - expect(await generateMetricsServer(false)).to.equal(''); + expect(await generateMetricsServer('test', false)).to.equal(''); } @test