diff --git a/README.md b/README.md index bc65bbd4..550fe390 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ default tracing behavior to nginx: - Create one span per request: - Service name is "nginx". - Operation name is "nginx.request". - - Resource name is `"$request_method $datadog_location"`, e.g. "GET /api". + - Resource name is `"$request_method $uri"`, e.g. "GET /api/book/0-345-24223-8/title". - Includes multiple `http.*` [tags][8]. Custom configuration can be specified via the [datadog](doc/API.md#datadog) diff --git a/doc/API.md b/doc/API.md index 949f136a..091e8049 100644 --- a/doc/API.md +++ b/doc/API.md @@ -125,6 +125,33 @@ those provided by this module). The location span is a span created in addition to the request span. See `datadog_trace_locations`. +### `datadog_resource_name` + +- **syntax** `datadog_resource_name ` +- **default**: `$request_method $uri`, e.g. "GET /api/book/0-345-24223-8/title" +- **context**: `http`, `server`, `location`, `if` + +Set the request span's "resource name" (sometimes called "endpoint") to the +result of evaluating the specified `` in the context of the +current request. `` is a string that may contain +`$`-[variables][2] (including those provided by this module). + +The request span is the span created while processing a request. + +### `datadog_location_resource_name` + +- **syntax** `datadog_location_resource_name ` +- **default**: `$request_method $uri`, e.g. "GET /api/book/0-345-24223-8/title" +- **context**: `http`, `server`, `location`, `if` + +Set the location span's "resource name" (sometimes called "endpoint") to the +result of evaluating the specified `` in the context of the +current request. `` is a string that may contain +`$`-[variables][2] (including those provided by this module). + +The location span is a span created in addition to the request span. See +`datadog_trace_locations`. + ### `datadog_trust_incoming_span` - **syntax** `datadog_trust_incoming_span on|off` diff --git a/example/services/nginx/nginx.conf b/example/services/nginx/nginx.conf index 8af80abf..01a4c70f 100644 --- a/example/services/nginx/nginx.conf +++ b/example/services/nginx/nginx.conf @@ -32,6 +32,10 @@ http { datadog_trace_locations on; # This tag will be on the location-specific span. datadog_tag special.tag "The URI is $uri"; + # The resource name is customizable for both the request span and + # the location span. + datadog_resource_name "request URI $uri"; + datadog_location_resource_name "location URI $uri"; proxy_pass http://http:8080; } diff --git a/src/datadog_conf.h b/src/datadog_conf.h index e81f5aa2..7fb99304 100644 --- a/src/datadog_conf.h +++ b/src/datadog_conf.h @@ -78,6 +78,8 @@ struct datadog_loc_conf_t { ngx_flag_t enable_locations; NgxScript operation_name_script; NgxScript loc_operation_name_script; + NgxScript resource_name_script; + NgxScript loc_resource_name_script; ngx_flag_t trust_incoming_span; ngx_array_t *tags; // `response_info_script` is a script that can contain variables that refer diff --git a/src/datadog_directive.cpp b/src/datadog_directive.cpp index 9f588ae9..2a3e6544 100644 --- a/src/datadog_directive.cpp +++ b/src/datadog_directive.cpp @@ -487,6 +487,17 @@ char *set_datadog_location_operation_name(ngx_conf_t *cf, ngx_command_t *command return set_script(cf, command, loc_conf->loc_operation_name_script); } +char *set_datadog_resource_name(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept { + auto loc_conf = static_cast(conf); + return set_script(cf, command, loc_conf->resource_name_script); +} + +char *set_datadog_location_resource_name(ngx_conf_t *cf, ngx_command_t *command, + void *conf) noexcept { + auto loc_conf = static_cast(conf); + return set_script(cf, command, loc_conf->loc_resource_name_script); +} + char *toggle_opentracing(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept { const auto loc_conf = static_cast(conf); const auto values = static_cast(cf->args->elts); diff --git a/src/datadog_directive.h b/src/datadog_directive.h index 96be9157..900c9a5f 100644 --- a/src/datadog_directive.h +++ b/src/datadog_directive.h @@ -38,6 +38,11 @@ char *set_datadog_operation_name(ngx_conf_t *cf, ngx_command_t *command, void *c char *set_datadog_location_operation_name(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept; +char *set_datadog_resource_name(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept; + +char *set_datadog_location_resource_name(ngx_conf_t *cf, ngx_command_t *command, + void *conf) noexcept; + char *toggle_opentracing(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept; char *datadog_enable(ngx_conf_t *cf, ngx_command_t *command, void *conf) noexcept; diff --git a/src/ngx_http_datadog_module.cpp b/src/ngx_http_datadog_module.cpp index 700e04ca..7f518581 100644 --- a/src/ngx_http_datadog_module.cpp +++ b/src/ngx_http_datadog_module.cpp @@ -176,6 +176,20 @@ static ngx_command_t datadog_commands[] = { 0, nullptr), + { ngx_string("datadog_resource_name"), + anywhere | NGX_CONF_TAKE1, + set_datadog_resource_name, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + nullptr}, + + { ngx_string("datadog_location_resource_name"), + anywhere | NGX_CONF_TAKE1, + set_datadog_location_resource_name, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + nullptr}, + DEFINE_COMMAND_WITH_OLD_ALIAS( "datadog_trust_incoming_span", "opentracing_trust_incoming_span", @@ -295,10 +309,13 @@ static ngx_int_t datadog_module_init(ngx_conf_t *cf) noexcept { if (tags.empty()) return NGX_OK; main_conf->tags = ngx_array_create(cf->pool, tags.size(), sizeof(datadog_tag_t)); if (!main_conf->tags) return NGX_ERROR; - for (const auto &tag : tags) + for (const auto &tag : tags) { if (add_datadog_tag(cf, main_conf->tags, to_ngx_str(tag.first), to_ngx_str(tag.second)) != - NGX_CONF_OK) + NGX_CONF_OK) { return NGX_ERROR; + } + } + return NGX_OK; } @@ -398,13 +415,13 @@ static void *create_datadog_loc_conf(ngx_conf_t *conf) noexcept { namespace { -// Merge the specified `previous` operation name script into the specified -// `current` operation name script in the context of the specified `conf`. If -// `current` does not have a value and `previous` does, then `previous` will be -// used. If neither has a value, then a hard-coded default will be used. -// Return `NGX_CONF_OK` on success, or another value otherwise. -char *merge_operation_name_script(ngx_conf_t *conf, NgxScript &previous, NgxScript ¤t, - ot::string_view default_pattern) { +// Merge the specified `previous` script into the specified `current` script in +// the context of the specified `conf`. If `current` does not have a value and +// `previous` does, then `previous` will be used. If neither has a value, then +// the specified `default_pattern` will be used. Return `NGX_CONF_OK` on +// success, or another value otherwise. +char *merge_script(ngx_conf_t *conf, NgxScript &previous, NgxScript ¤t, + ot::string_view default_pattern) { if (current.is_valid()) { return NGX_CONF_OK; } @@ -420,28 +437,6 @@ char *merge_operation_name_script(ngx_conf_t *conf, NgxScript &previous, NgxScri return NGX_CONF_OK; } -char *merge_response_info_script(ngx_conf_t *conf, NgxScript &previous, NgxScript ¤t) { - // The response info script is the same for each `datadog_loc_conf_t`. The only - // reason it's a member of `datadog_loc_conf_t` is so that it is available at - // the end of each request, when we might like to inspect e.g. response - // headers. - if (current.is_valid()) { - return NGX_CONF_OK; - } - - if (!previous.is_valid()) { - // Response header inspection is not currently used by this module, but I'm - // leaving the boilerplate for future use. - const ngx_int_t rc = previous.compile(conf, ngx_string("")); - if (rc != NGX_OK) { - return (char *)NGX_CONF_ERROR; - } - } - - current = previous; - return NGX_CONF_OK; -} - } // namespace //------------------------------------------------------------------------------ @@ -455,19 +450,26 @@ static char *merge_datadog_loc_conf(ngx_conf_t *cf, void *parent, void *child) n ngx_conf_merge_value(conf->enable_locations, prev->enable_locations, TracingLibrary::trace_locations_by_default()); + if (const auto rc = merge_script(cf, prev->operation_name_script, conf->operation_name_script, + TracingLibrary::default_request_operation_name_pattern())) { + return rc; + } if (const auto rc = - merge_operation_name_script(cf, prev->operation_name_script, conf->operation_name_script, - TracingLibrary::default_request_operation_name_pattern())) { + merge_script(cf, prev->loc_operation_name_script, conf->loc_operation_name_script, + TracingLibrary::default_location_operation_name_pattern())) { return rc; } - if (const auto rc = merge_operation_name_script( - cf, prev->loc_operation_name_script, conf->loc_operation_name_script, - TracingLibrary::default_location_operation_name_pattern())) { + if (const auto rc = merge_script(cf, prev->resource_name_script, conf->resource_name_script, + TracingLibrary::default_resource_name_pattern())) { + return rc; + } + if (const auto rc = + merge_script(cf, prev->loc_resource_name_script, conf->loc_resource_name_script, + TracingLibrary::default_resource_name_pattern())) { return rc; } - if (const auto rc = - merge_response_info_script(cf, prev->response_info_script, conf->response_info_script)) { + merge_script(cf, prev->response_info_script, conf->response_info_script, "")) { return rc; } @@ -510,8 +512,8 @@ static char *merge_datadog_loc_conf(ngx_conf_t *cf, void *parent, void *child) n *tag = kv.second; } else { - datadog_tag_t *tag = (datadog_tag_t *)conf->tags->elts; - tag[index] = kv.second; + datadog_tag_t *tags = (datadog_tag_t *)conf->tags->elts; + tags[index] = kv.second; } index++; diff --git a/src/request_tracing.cpp b/src/request_tracing.cpp index 807688bb..b9e7de90 100644 --- a/src/request_tracing.cpp +++ b/src/request_tracing.cpp @@ -33,6 +33,24 @@ static std::string get_request_operation_name(ngx_http_request_t *request, return to_string(core_loc_conf->name); } +static std::string get_loc_resource_name(ngx_http_request_t *request, + const datadog_loc_conf_t *loc_conf) { + if (loc_conf->loc_resource_name_script.is_valid()) { + return to_string(loc_conf->loc_resource_name_script.run(request)); + } else { + return "[invalid_resource_name_pattern]"; + } +} + +static std::string get_request_resource_name(ngx_http_request_t *request, + const datadog_loc_conf_t *loc_conf) { + if (loc_conf->resource_name_script.is_valid()) { + return to_string(loc_conf->resource_name_script.run(request)); + } else { + return "[invalid_resource_name_pattern]"; + } +} + static void add_script_tags(ngx_array_t *tags, ngx_http_request_t *request, ot::Span &span) { if (!tags) return; auto add_tag = [&](const datadog_tag_t &tag) { @@ -153,6 +171,7 @@ void RequestTracing::on_exit_block(std::chrono::steady_clock::time_point finish_ // // See on_log_request below span_->SetOperationName(get_loc_operation_name(request_, core_loc_conf_, loc_conf_)); + span_->SetTag("resource.name", get_loc_resource_name(request_, loc_conf_)); span_->Finish({ot::FinishTimestamp{finish_timestamp}}); } else { @@ -178,6 +197,7 @@ void RequestTracing::on_log_request() { auto core_loc_conf = static_cast( ngx_http_get_module_loc_conf(request_, ngx_http_core_module)); request_span_->SetOperationName(get_request_operation_name(request_, core_loc_conf, loc_conf_)); + request_span_->SetTag("resource.name", get_request_resource_name(request_, loc_conf_)); // Note: At this point, we could run an `NginxScript` to interrogate the // proxied server's response headers, e.g. to retrieve a deferred sampling diff --git a/src/tracing_library.cpp b/src/tracing_library.cpp index cf9cc228..8effb350 100644 --- a/src/tracing_library.cpp +++ b/src/tracing_library.cpp @@ -198,10 +198,11 @@ std::unordered_map TracingLibrary::default_tags() { // See // https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#common-attributes {"http.useragent", "$http_user_agent"}, - {"resource.name", "$request_method $datadog_location"}, {"nginx.location", "$datadog_location"}}; } +string_view TracingLibrary::default_resource_name_pattern() { return "$request_method $uri"; } + bool TracingLibrary::tracing_on_by_default() { return true; } bool TracingLibrary::trace_locations_by_default() { return false; } diff --git a/src/tracing_library.h b/src/tracing_library.h index 2c29d620..457d0508 100644 --- a/src/tracing_library.h +++ b/src/tracing_library.h @@ -111,6 +111,13 @@ struct TracingLibrary { // (realistically this means that it will refer to a string literal). static string_view default_location_operation_name_pattern(); + // Return the pattern of an nginx variable script that will be used for the + // resource name of spans that do not have a resource name configured in the + // nginx configuration. Note that the storage to which the returned value + // refers must outlive any usage of the return value (realistically this + // means that it will refer to a string literal). + static string_view default_resource_name_pattern(); + // Return a mapping of tag name to nginx variable script pattern. These // tags will be defined automatically during configuration as if they // appeared in the nginx configuration file's http section, e.g. diff --git a/test/cases/resource_name/README.md b/test/cases/resource_name/README.md new file mode 100644 index 00000000..e5438576 --- /dev/null +++ b/test/cases/resource_name/README.md @@ -0,0 +1,11 @@ +These tests verify that the "resource name" ("resource") of spans produced by +the module are as configured. + +Resource names can be set using the `datadog_resource_name` directive. If the +directive is not used, then resource name takes on a default value. See +`TracingLibrary::default_resource_name_pattern()`. + +The resource name of request spans and location spans can be set separately. For +location spans, there is the `datadog_location_resource_name` directive. + +These tests closely resemble those in [../operation_name](../operation_name). diff --git a/test/cases/resource_name/conf/default_in_location.conf b/test/cases/resource_name/conf/default_in_location.conf new file mode 100644 index 00000000..c3ab9864 --- /dev/null +++ b/test/cases/resource_name/conf/default_in_location.conf @@ -0,0 +1,16 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + + location /foo { + datadog_trace_locations on; + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/default_in_request.conf b/test/cases/resource_name/conf/default_in_request.conf new file mode 100644 index 00000000..063b679c --- /dev/null +++ b/test/cases/resource_name/conf/default_in_request.conf @@ -0,0 +1,15 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + + location /foo { + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_location_at_http.conf b/test/cases/resource_name/conf/manual_in_location_at_http.conf new file mode 100644 index 00000000..69f33758 --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_location_at_http.conf @@ -0,0 +1,18 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + datadog_trace_locations on; + datadog_location_resource_name "fuzzy.pumpkin"; + + server { + listen 80; + + location /foo { + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_location_at_location.conf b/test/cases/resource_name/conf/manual_in_location_at_location.conf new file mode 100644 index 00000000..4a8514e7 --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_location_at_location.conf @@ -0,0 +1,18 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + datadog_trace_locations on; + + server { + listen 80; + + location /foo { + datadog_location_resource_name "fuzzy.pumpkin"; + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_location_at_server.conf b/test/cases/resource_name/conf/manual_in_location_at_server.conf new file mode 100644 index 00000000..81a049ce --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_location_at_server.conf @@ -0,0 +1,19 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + datadog_trace_locations on; + + server { + listen 80; + + datadog_location_resource_name "fuzzy.pumpkin"; + + location /foo { + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_request_at_http.conf b/test/cases/resource_name/conf/manual_in_request_at_http.conf new file mode 100644 index 00000000..f7ba196b --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_request_at_http.conf @@ -0,0 +1,17 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + datadog_resource_name "fuzzy.pumpkin"; + + server { + listen 80; + + location /foo { + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_request_at_location.conf b/test/cases/resource_name/conf/manual_in_request_at_location.conf new file mode 100644 index 00000000..cd229640 --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_request_at_location.conf @@ -0,0 +1,16 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + + location /foo { + datadog_resource_name "fuzzy.pumpkin"; + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/conf/manual_in_request_at_server.conf b/test/cases/resource_name/conf/manual_in_request_at_server.conf new file mode 100644 index 00000000..b2e45624 --- /dev/null +++ b/test/cases/resource_name/conf/manual_in_request_at_server.conf @@ -0,0 +1,17 @@ +load_module modules/ngx_http_datadog_module.so; + +events { + worker_connections 1024; +} + +http { + server { + listen 80; + + datadog_resource_name "fuzzy.pumpkin"; + + location /foo { + proxy_pass http://http:8080; + } + } +} diff --git a/test/cases/resource_name/test_resource_name.py b/test/cases/resource_name/test_resource_name.py new file mode 100644 index 00000000..fce5a3a1 --- /dev/null +++ b/test/cases/resource_name/test_resource_name.py @@ -0,0 +1,164 @@ +from .. import case +from .. import formats + +from pathlib import Path + + +class TestResourceName(case.TestCase): + + def test_default_in_request(self): + """Verify that the request span's resource name matches the default + pattern when not otherwise configured. + """ + + # The default value is determined by + # `TracingLibrary::default_resource_name_pattern`. + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(0, len(rest), chunk) + self.assertEqual('GET /foo', first['resource'], chunk) + + return self.run_resource_name_test('./conf/default_in_request.conf', + on_chunk) + + def test_default_in_location(self): + """Verify that both the request span and the location span's resource + name match the default pattern when not otherwise configured. + """ + + # The default values are determined by + # `TracingLibrary::default_resource_name_pattern`. + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(1, len(rest), chunk) + self.assertEqual('GET /foo', first['resource'], chunk) + self.assertEqual('GET /foo', rest[0]['resource'], chunk) + + return self.run_resource_name_test('./conf/default_in_location.conf', + on_chunk) + + def test_manual_in_request_at_location(self): + """Verify that using the `datadog_resource_name` directive in a + `location` block causes the resulting request span's resource name to + match the setting. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(0, len(rest), chunk) + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_request_at_location.conf', on_chunk) + + def test_manual_in_request_at_server(self): + """Verify that using the `datadog_resource_name` directive in a + `server` block causes the resulting request span's resource name to + match the setting. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(0, len(rest), chunk) + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_request_at_server.conf', on_chunk) + + def test_manual_in_request_at_http(self): + """Verify that using the `datadog_resource_name` directive in a `http` + block causes the resulting request span's resource name to match the + setting. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(0, len(rest), chunk) + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_request_at_http.conf', on_chunk) + + def test_manual_in_location_at_location(self): + """Verify that using the `datadog_location_resource_name` directive in + a `location` block causes the resulting location span's resource name + to match the setting. Note that the `datadog_trace_locations on` + directive must also be used. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(1, len(rest), chunk) + # We assume that the location span comes first, because it finishes + # first. + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_location_at_location.conf', on_chunk) + + def test_manual_in_location_at_server(self): + """Verify that using the `datadog_location_resource_name` directive in + a `server` block causes the resulting location span's resource name + to match the setting. Note that the `datadog_trace_locations on` + directive must also be used. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(1, len(rest), chunk) + # We assume that the location span comes first, because it finishes + # first. + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_location_at_server.conf', on_chunk) + + def test_manual_in_location_at_http(self): + """Verify that using the `datadog_location_resource_name` directive in + an `http` block causes the resulting location span's resource name to + match the setting. Note that the `datadog_trace_locations on` + directive must also be used. + """ + + def on_chunk(chunk): + first, *rest = chunk + self.assertEqual(1, len(rest), chunk) + # We assume that the location span comes first, because it finishes + # first. + self.assertEqual('fuzzy.pumpkin', first['resource'], chunk) + + return self.run_resource_name_test( + './conf/manual_in_location_at_http.conf', on_chunk) + + def run_resource_name_test(self, conf_relative_path, on_chunk): + conf_path = Path(__file__).parent / conf_relative_path + conf_text = conf_path.read_text() + status, log_lines = self.orch.nginx_replace_config( + conf_text, conf_path.name) + self.assertEqual(0, status, log_lines) + + # Clear any outstanding logs from the agent. + self.orch.sync_service('agent') + + status, _ = self.orch.send_nginx_http_request('/foo') + self.assertEqual(200, status) + + # Reload nginx to force it to send its traces. + self.orch.reload_nginx() + + log_lines = self.orch.sync_service('agent') + # Find the trace that came from nginx, and pass its chunks (groups of + # spans) to the callback. + found_nginx_trace = False + for line in log_lines: + trace = formats.parse_trace(line) + if trace is None: + # not a trace; some other logging + continue + for chunk in trace: + if chunk[0]['service'] != 'nginx': + continue + found_nginx_trace = True + on_chunk(chunk) + + self.assertTrue(found_nginx_trace) diff --git a/test/services/nginx/Dockerfile b/test/services/nginx/Dockerfile index b432d974..715e816e 100644 --- a/test/services/nginx/Dockerfile +++ b/test/services/nginx/Dockerfile @@ -20,3 +20,6 @@ COPY ngx_http_datadog_module.so ${NGINX_MODULES_DIRECTORY} ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"] + +# Override the default configuration with a minimal one. +COPY ./nginx.conf /etc/nginx/nginx.conf diff --git a/test/services/nginx/nginx.conf b/test/services/nginx/nginx.conf new file mode 100644 index 00000000..01777180 --- /dev/null +++ b/test/services/nginx/nginx.conf @@ -0,0 +1,11 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +error_log /dev/stderr; + +http { + access_log /dev/stdout; +}