Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy: logging add custom access log options (JSON, Service vars, NGX vars) #1089

Merged
merged 11 commits into from
Jul 24, 2019

Conversation

eloycoto
Copy link
Contributor

@eloycoto eloycoto commented Jul 5, 2019

Some users requested different ways to log the access log with more metadata,
different formats or conditional logging based on multiple request values.

This policy address this, two new variables are now set, where to allow or disallow
to print a custom log message, and another one extened_access_log just store
all the information to print that.

The policy has multiple options, here a few examples:

Custom log format

{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false
    "custom_logging": "\"{{request}}\" to service {{service.id}} and {{service.name}}",
  }
}

Only log the entry if the status is 200

{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false
    "custom_logging": "\"{{request}}\" to service {{service.id}} and {{service.name}}",
    "condition": {
      "operations": [
        {"op": "==", "match": "{{status}}", "match_type": "liquid", "value": "200"}
      ],
      "combine_op": "and"
    }
  }
}

This pr fixed #1082 and THREESCALE-1234 and THREESCALE-2876

"name": "apicast.policy.logging",
"configuration": {
"enable_access_logs": false,
"custom_logging": "{\"time_local\": \"{{time_local}}\", \"host\" : \"{{host}}\"}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this would work. It is exploitable by sending values like \" in headers or the host header. It really needs to be properly serialized.

@eloycoto
Copy link
Contributor Author

eloycoto commented Jul 8, 2019

Hi,

I had a chat with Michal today about this policy, there are a couple of issues
about the default logging all described in the issue #1082

My proposal for default logging:##

Two new ENV variables:

CUSTOM_ACCESS_LOG=true
CUSTOM_ACCESS_LOG_FORMAT='{{ host }}'

So, if the CUSTOM_ACCESS_LOG is enabled, a new policy like
DEFAULT_POLICIES
will be enabled, the the custom_logging parameter will be the
CUSTOM_ACCESS_LOG_FORMAT env variable.

This provides a few good things:

  • All keeps in a policy.
  • Can render all the variables, context, service, response/request headers.

As discussed, this behaviour is not good enough, policies can't be loaded by
ENV, and that rule can't be break.

ENV variable to modify the access_log:

This variable will set the access log by default using ENV variable, if not
default one, the example will be like this:

Line of Code: 
https://github.com/3scale/APIcast/blob/master/gateway/http.d/apicast.conf.liquid#L1

Current code: 

log_format time '[$time_local] $host:$server_port $remote_addr:$remote_port "$request" $status $body_bytes_sent ($request_time) $post_action_impact';

Updated code:
log_format {{LOG_FORMAT | '`'[$time_local] $host:$server_port $remote_addr:$remote_port "$request" $status $body_bytes_sent ($request_time) $post_action_impact\''}};

This has the following strengths:

  • No liquid rendering, all happens in Nginx.
  • Default logging and no policies are loaded.

This has the following weaknesses:

  • Users in this case need to write the ENV variable in nginx format $host and
    in the policy with the liquid format {{host}}.
  • All context information, services, headers, etc.. are lost here.
  • Json format with fields will be lost

Json render in logs:

Michal also commented that the current JSON render is not a valid option, we
need to deal with JSON using a Lua table and render one by one, and create the
JSON using cjson:

Example policy configuration:

{ 
    log_format: [
        "host": "{{host}}", 
        "time_local": "{{time_local}}",
        "method": "{{method}}",
    ]
}

This has the following weaknesses:

  • Need to call Liquid by each key, instead of a single call.
  • For default access with the change in liquid is almost impossible, is
    generated by NGINX, and no Json render by default in Nginx.(There is a
    extension)
  • For ENV variables for the access log you need to some kind of format like
    export DEFAULT_LOG_FORMAT=host={{host}},time={{time_local}},method={{method}}
  • We need to create the form to enable or not JSON, I guess that some users need
    to have a standard log format and not all users need JSON logs.

Workaround:

Use escape in Liquid log, example:

{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false,
    "custom_logging": "{\"time_local\": \"{{time_local | escape}}\", \"host\" : \"{{host|escape}}\"}",
  }
}

Please, share your thoughts here,

Thanks
Eloy!

@mikz
Copy link
Contributor

mikz commented Jul 9, 2019

Customers already can enforce policies for all services by using the Account Management API and just add them to the policy chain. All that can be automated without changing APIcast at all.

The default logging is just a subset of a feature that would enable configuring any policy by env. I think we should not make a specific case for logging. Either go the full way and have a general solution for all policies or don't do it at all. This is already possible by using a Lua script (environment file). See our APIcast Cloud Hosted: https://github.com/3scale/apicast-cloud-hosted/blob/master/apicast/config/cloud_hosted.lua
Just make own image by using BuildConfig and configure policies globally as you want. I don't see the need to do it in a limited fashion through ENV variables.

Regarding the log_format customization, I don't get the drawbacks. Not all the Lua variables are accessible, but some are exposed (like service_id,

set $cached_key '';
set $credentials '';
set $usage '';
set $service_id '';
set $proxy_pass '';
set $secret_token '';
set $backend_host 'backend';
set $backend_authentication_type '';
set $backend_authentication_value '';
set $version '';
set $real_url '';
). Also all the headers and the request/response/upstream information is available through nginx variables. JSON formatting can be done using escape=json and just making the json format like https://stackoverflow.com/questions/25049667/how-to-generate-a-json-log-from-nginx for example.

Regarding the JSON serialization. I think that proper JSON serialization is the only way how to do JSON formatting from Lua. We don't know how big performance drawback it would be yet, so I'd not say it is an issue to serialize them in a loop. Also, we just have to bite the bullet at some point and optimize the liquid library to be JITed and the benefits will be everywhere.

To summarize it:

To have a default policy somewhere in the global chain, just use the environment file like the APIcast Cloud Hosted. That can be done just by mounting it via a ConfigMap or building it into the image.

But for a simpler, per deployment configuration, just allowing to set the custom log format and it's escaping by ENV variables is enough for most cases. I think all the request information that makes sense in logs is available by nginx variables.

JSON serialization in Lua has to be done by cjson. We should measure the performance impact of liquid processing in a loop, but this is done on other places already (like adding headers).

@eloycoto
Copy link
Contributor Author

eloycoto commented Jul 9, 2019

@mikz sorry,

I still have two questions regarding this:

  1. escape=json will work ok, but I have a question around this, what happens if the log line is not json? do you want to have extendedLogJson=true|false and extendedLog=true for normal log and define 3 kind of access log?

  2. If we use buildConfig we can use logging policy and users can use that, I'm happy with that.

Regards.

@mikz
Copy link
Contributor

mikz commented Jul 9, 2019

A logging policy should use custom format or JSON (by cjson), but in both cases should be escape=none.
If someone would want to change the default logging to a different format (like JSON), they should also be able to set the escaping. Then it is entirely up to the user what they want to do.

Not only BuildConfig, but even just mounting the file from ConfigMap works.

@eloycoto
Copy link
Contributor Author

eloycoto commented Jul 9, 2019

Ok,

To summarize:

  1. I'll document in the policy how to set up a buildconfig with a default
    logging and load policy in all services. Also the integration test.
  2. I'll change the logging configuration to something like this:

Custom logging with single log line data:

{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false,
    "custom_logging": "[{{time_local}}] {{host}}:{{server_port}}"
  }
}

Custom logging with json log line data:

{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false,
    "enable_json_logs": true,
    "json_object_config": [
      {
        "key": "host",
        "value": "{{host}}",
        "value_type": "liquid"
      },
      {
        "key": "time",
        "value": "{{time_local}}",
        "value_type": "liquid"
      },
      {
        "key": "custom",
        "value": "custom_method",
        "value_type": "plain"
      }
    ]
  }
}

Form example

In case of cjson error, log line will be disabled and nothing will be logged.

@magnusvage
Copy link

Hi!

@eloycoto pointed me this way. I am not very worried about the technical solution. I'm sure you'll come up with a way that gets the job done and is workable for both you and us. However, @mikz you mention that "not all Lua variables are exposed, but some". Is there an exhaustive list somewhere over which variables are available? We have pretty clear requirements regarding what we need to put in the access log. Would it be possible to get service system name and method/metric system name into the access log row with the suggested solution above?

@eloycoto eloycoto force-pushed the Issue-1082 branch 3 times, most recently from d3a5e30 to 40f8387 Compare July 10, 2019 13:47
@eloycoto eloycoto requested review from davidor and mikz July 10, 2019 13:50
@eloycoto eloycoto force-pushed the Issue-1082 branch 3 times, most recently from c19491e to bcccc9d Compare July 10, 2019 16:42
@mikz
Copy link
Contributor

mikz commented Jul 11, 2019

@magnusvage

set $cached_key '';
set $credentials '';
set $usage '';
set $service_id '';
set $proxy_pass '';
set $secret_token '';
set $backend_host 'backend';
set $backend_authentication_type '';
set $backend_authentication_value '';
set $version '';
set $real_url '';
set $ctx_ref -1;
set $post_action_impact '';
set $original_request_id '';
those should be available. It does not contain service name, as it is not multi tenant, but rather just service id. The usage is the query encoded usage reported to 3scale backend.

@davidor
Copy link
Contributor

davidor commented Jul 16, 2019

I didn't know that tool @mikz . It would be good to add those checks to circleCI.

header in the request, or `{{res.headers.FOO}}` to retrieve FOO header on
response.
- Service information, as `{{service.id}}` and all service propertias as the
`THREESCALE_CONFIG_FILE` parameter provided
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is a bit confusing. It mentions THREESCALE_CONFIG_FILE, but it also works with THREESCALE_PORTAL_ENDPOINT.

- Service information, as `{{service.id}}` and all service propertias as the
`THREESCALE_CONFIG_FILE` parameter provided

## Caveats
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to put this section below the Examples one. The examples one introduces the attribute fields mentioned here so I think that placing the Examples section before this one gives the reader more context. What do you think?

}
]
}
--- upstream
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is not needed in this test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with the tests below.

--- no_error_log eval
[qr/\[error/, qr/GET \/ HTTP\/1.1\" 200/]

=== TEST 6: service uses a custom access log format without a valid condition
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition is valid, but it evaluates to false.

GET /
--- error_code: 200
--- error_log eval
[ qr/^Status\:\:200/ ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check the service ID.

return self:log_dump_line(extended_context)
end

function _M.enable_extended_access_log()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these functions be local? I think all of them should except the policy phases.

ngx.var[ngx_var_access_logs_enabled] = self.enable_access_logs_val
if not (self.custom_logging or self.enable_json_logs) then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can define use_default_access_logs that returns this not ( ... or ... ).
Then we could write if use_default_access_logs() then return end.


return self
end

function _M:log()
local function get_request_context(context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to define these headers here instead of reusing ngx_variable ?
I think everything on ngx_variable should be available. We should define there the response headers, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just because it is easy to provide to users information about the request and response in an easy way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this limits us to the information in the service plus the headers.
I guess it's fine for now but in the future, users might ask for other info that we have in ngx_variable like it happened for other policies.

assert.equals(0, ngx.var.access_logs_enabled)
assert.equals(1, ngx.var.extended_access_logs_enabled)
assert.equals(ngx.var.extended_access_log, expected_json)
assert.spy(logging.log_dump_json).was.called()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? The previous assert already verifies that a JSON was generated, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to make sure that the call to the correct function for future changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. Shouldn't we just care about the final result? In that case, we want to verify that the log generated is the JSON we expect. Not sure why we need to assert that a specific private method was called. If in the future we change the internals of this class returning the same result, this test will break.

self.json_object_config = config.json_object_config or {}

if config.condition then
ngx.log(ngx.DEBUG, 'Enabling extended log with conditions')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a good candidate to be extracted into its own function.

@eloycoto eloycoto force-pushed the Issue-1082 branch 3 times, most recently from 69478a3 to 93bc57d Compare July 16, 2019 15:31
@eloycoto eloycoto requested a review from davidor July 16, 2019 15:35
}
```

<<<<<<< HEAD
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️

}
]
}
--- upstream
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it appears on every test starting from this one and it's not needed in any of them.

end

--- log_dump_json: returns an string with the json output.
function _M:log_dump_json(extended_context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these functions be local? I mean all of them except log().



function _M:use_default_access_logs()
if (self.custom_logging or self.enable_json_logs) then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified:

return not (self.custom_logging or self.enable_json_logs)

assert.equals(0, ngx.var.access_logs_enabled)
assert.equals(1, ngx.var.extended_access_logs_enabled)
assert.equals(ngx.var.extended_access_log, expected_json)
assert.spy(logging.log_dump_json).was.called()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. Shouldn't we just care about the final result? In that case, we want to verify that the log generated is the JSON we expect. Not sure why we need to assert that a specific private method was called. If in the future we change the internals of this class returning the same result, this test will break.

gateway/http.d/apicast.conf.liquid Show resolved Hide resolved
gateway/src/apicast/policy/logging/readme.md Show resolved Hide resolved
enable_json_logs = true,
json_object_config = {
{ key = "foo", value="{{foo}}", value_type="liquid"},
{ key = "bar", value="barValue", value_type="liquid"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot about this one.


return self
end

function _M:log()
local function get_request_context(context)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this limits us to the information in the service plus the headers.
I guess it's fine for now but in the future, users might ask for other info that we have in ngx_variable like it happened for other policies.

Some users requested different ways to log access log with more metadata,
different formats or conditional logging based on multiple request values.

This policy address this, two new variables are now set, where allow or disallow
to print a custom log message, and another one `extened_access_log` just store
all the information to print that.

Policy has multiple options, here a few examples:

Custom log format
```
{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false
    "custom_logging": "\"{{request}}\" to service {{service.id}} and {{service.name}}",
  }
}
```

Only log the entry if status is 200

```
{
  "name": "apicast.policy.logging",
  "configuration": {
    "enable_access_logs": false
    "custom_logging": "\"{{request}}\" to service {{service.id}} and {{service.name}}",
    "condition": {
      "operations": [
        {"op": "==", "match": "{{status}}", "match_type": "liquid", "value": "200"}
      ],
      "combine_op": "and"
    }
  }
}
```

This commit fixed 3scale#1082 and THREESCALE-1234 and THREESCALE-2876

Signed-off-by: Eloy Coto <[email protected]>
@@ -0,0 +1,172 @@
# Logging policy

This policy has two primary purposes: one is to enable and disable access log
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This policy has two purposes: one is to enable and disable access log

# Logging policy

This policy has two primary purposes: one is to enable and disable access log
output, and the second one is to be able to create a custom access log format
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output, and the other is to create a custom access log format


## Exported variables

Liquid templating can be used on custom logging, the exported variables are:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Liquid templating can be used on custom logging. The exported variables include:


## Examples

### Disable access log:
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Disabling access logs

}
```

### Enable custom access log:
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Enabling custom access logs

}
```

### Enable custom access log with the service ID:
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Enabling custom access logs with the service ID

}
```

### Write access log in JSON format:
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Configuring access logs in JSON format

}
```

### Write a custom access log only for a successful request.
Copy link
Contributor

@porueesq porueesq Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Configuring a custom access log only for a successful request

}
```

### Write a custom access log where reponse status match 200 or 500.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Customizing access logs where reponse status match 200 or 500

## Caveats

- If `custom_logging` or `enable_json_logs` property is enabled, default access
log will be dissabled.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log will be disabled.


- If `custom_logging` or `enable_json_logs` property is enabled, default access
log will be dissabled.
- If `enable_json_logs` is enabled, `custom_logging` field will be omitted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If enable_json_logsis enabled,custom_logging field will be omitted.

log will be dissabled.
- If `enable_json_logs` is enabled, `custom_logging` field will be omitted

## Global configuration for all services.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## Global configuration for all services


## Global configuration for all services.

Logging options can be useful in all services, to avoid having issues with logs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all services, logging options help to avoid having issues with logs

## Global configuration for all services.

Logging options can be useful in all services, to avoid having issues with logs
that are not correctly formated in other services, a custom Apicast environment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that are not correctly formated in other services, a custom APIcast environment

Copy link
Contributor

@porueesq porueesq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments and suggestions.

}
```

To run Apicast with this specific environment:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIcast

```

Key concepts of the docker command:
- Current lua file need to be shared to the container `-v $(pwd):/config`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current Lua file must be shared to the container -v $(pwd):/config``


Key concepts of the docker command:
- Current lua file need to be shared to the container `-v $(pwd):/config`
- `APICAST_ENVIRONMENT` variable need to be set to the lua file that is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

``APICAST_ENVIRONMENT variable must be set to the Lua file that is

@eloycoto eloycoto requested a review from porueesq July 23, 2019 16:59
output, and the second one is to be able to create a custom access log format
for each service and be able to set conditions to write custom access log.
This policy has two purposes: one is to enable and disable access log output,
and and the other is to create a custom access log format for each service and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the other is to create a custom access log format for each service, as well as to

Remove the first and.

@@ -20,7 +20,7 @@ response.

## Examples

### Disable access log:
### Disabling access log:
Copy link
Contributor

@porueesq porueesq Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove colon : from all headings.
### Disabling access logs

@@ -31,7 +31,7 @@ response.
}
```

### Enable custom access log:
### Enabling custom access log:
Copy link
Contributor

@porueesq porueesq Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove colon.
### Enabling custom access logs

@@ -43,7 +43,7 @@ response.
}
```

### Enable custom access log with the service ID:
### Enabling custom access log with the service ID:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

### Enabling custom access logs with service identifier

Copy link
Contributor

@porueesq porueesq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some further refinements.

@eloycoto eloycoto requested a review from porueesq July 24, 2019 16:12
@davidor davidor merged commit 41ad2c2 into 3scale:master Jul 24, 2019
@davidor davidor mentioned this pull request Aug 5, 2019
4 tasks
@magnusvage
Copy link

@davidor when can we expect these changes to be available for us users to try out?

@eloycoto
Copy link
Contributor Author

Hi @magnusvage

This policy is currently merged and working on master and will be released in the next version 3.7, that release should be out in a couple of months, but some extensive testing needs to be done, and maybe it's delayed, it's a bit early to say a date.

Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature: more logging variables available for the access log
5 participants