mirror of
https://github.com/traefik/traefik
synced 2026-02-03 11:10:33 +00:00
Add a new option to allow Stdio access logs alongsige OTLP logging
This commit is contained in:
@@ -10,6 +10,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||||||
| <a id="opt-accesslog" href="#opt-accesslog" title="#opt-accesslog">accesslog</a> | Access log settings. | false |
|
| <a id="opt-accesslog" href="#opt-accesslog" title="#opt-accesslog">accesslog</a> | Access log settings. | false |
|
||||||
| <a id="opt-accesslog-addinternals" href="#opt-accesslog-addinternals" title="#opt-accesslog-addinternals">accesslog.addinternals</a> | Enables access log for internal services (ping, dashboard, etc...). | false |
|
| <a id="opt-accesslog-addinternals" href="#opt-accesslog-addinternals" title="#opt-accesslog-addinternals">accesslog.addinternals</a> | Enables access log for internal services (ping, dashboard, etc...). | false |
|
||||||
| <a id="opt-accesslog-bufferingsize" href="#opt-accesslog-bufferingsize" title="#opt-accesslog-bufferingsize">accesslog.bufferingsize</a> | Number of access log lines to process in a buffered way. | 0 |
|
| <a id="opt-accesslog-bufferingsize" href="#opt-accesslog-bufferingsize" title="#opt-accesslog-bufferingsize">accesslog.bufferingsize</a> | Number of access log lines to process in a buffered way. | 0 |
|
||||||
|
| <a id="opt-accesslog-dualoutput" href="#opt-accesslog-dualoutput" title="#opt-accesslog-dualoutput">accesslog.dualoutput</a> | Enables access log output alongside OTLP. By default, this output is disabled when OTLP is configured. | false |
|
||||||
| <a id="opt-accesslog-fields-defaultmode" href="#opt-accesslog-fields-defaultmode" title="#opt-accesslog-fields-defaultmode">accesslog.fields.defaultmode</a> | Default mode for fields: keep | drop | keep |
|
| <a id="opt-accesslog-fields-defaultmode" href="#opt-accesslog-fields-defaultmode" title="#opt-accesslog-fields-defaultmode">accesslog.fields.defaultmode</a> | Default mode for fields: keep | drop | keep |
|
||||||
| <a id="opt-accesslog-fields-headers-defaultmode" href="#opt-accesslog-fields-headers-defaultmode" title="#opt-accesslog-fields-headers-defaultmode">accesslog.fields.headers.defaultmode</a> | Default mode for fields: keep | drop | redact | drop |
|
| <a id="opt-accesslog-fields-headers-defaultmode" href="#opt-accesslog-fields-headers-defaultmode" title="#opt-accesslog-fields-headers-defaultmode">accesslog.fields.headers.defaultmode</a> | Default mode for fields: keep | drop | redact | drop |
|
||||||
| <a id="opt-accesslog-fields-headers-names-name" href="#opt-accesslog-fields-headers-names-name" title="#opt-accesslog-fields-headers-names-name">accesslog.fields.headers.names._name_</a> | Override mode for headers | |
|
| <a id="opt-accesslog-fields-headers-names-name" href="#opt-accesslog-fields-headers-names-name" title="#opt-accesslog-fields-headers-names-name">accesslog.fields.headers.names._name_</a> | Override mode for headers | |
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ Traefik also supports the `OTEL_RESOURCE_ATTRIBUTES` env variable to set up the
|
|||||||
|
|
||||||
Access logs concern everything that happens to the requests handled by Traefik.
|
Access logs concern everything that happens to the requests handled by Traefik.
|
||||||
|
|
||||||
|
!!! note "Stdio logs are not enabled by default alongside OTLP exports"
|
||||||
|
If you would like Stdio access logs to be available, use [accessLog.dualOutput](#opt-accesslog-dualOutput) option.
|
||||||
|
|
||||||
### Configuration Example
|
### Configuration Example
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
@@ -195,6 +198,7 @@ accessLog:
|
|||||||
|
|
||||||
```sh tab="CLI"
|
```sh tab="CLI"
|
||||||
--accesslog=true
|
--accesslog=true
|
||||||
|
--accesslog.dualoutput=true
|
||||||
--accesslog.format=json
|
--accesslog.format=json
|
||||||
--accesslog.filters.statuscodes=200,300-302
|
--accesslog.filters.statuscodes=200,300-302
|
||||||
--accesslog.filters.retryattempts
|
--accesslog.filters.retryattempts
|
||||||
@@ -213,6 +217,7 @@ The section below describes how to configure Traefik access logs using the stati
|
|||||||
| Field | Description | Default | Required |
|
| Field | Description | Default | Required |
|
||||||
|:-----------|:--------------------------|:--------|:---------|
|
|:-----------|:--------------------------|:--------|:---------|
|
||||||
| <a id="opt-accesslog-filePath" href="#opt-accesslog-filePath" title="#opt-accesslog-filePath">`accesslog.filePath`</a> | By default, the access logs are written to the standard output.<br />You can configure a file path instead using the `filePath` option.| | No |
|
| <a id="opt-accesslog-filePath" href="#opt-accesslog-filePath" title="#opt-accesslog-filePath">`accesslog.filePath`</a> | By default, the access logs are written to the standard output.<br />You can configure a file path instead using the `filePath` option.| | No |
|
||||||
|
| <a id="opt-accesslog-dualOutput" href="#opt-accesslog-dualOutput" title="#opt-accesslog-dualOutput">`accesslog.dualOutput`</a> | Force Stdio logging, even if OTLP is configured. By default, Stdio logging is disabled when OTLP is enabled for performance reasons. | false | No |
|
||||||
| <a id="opt-accesslog-format" href="#opt-accesslog-format" title="#opt-accesslog-format">`accesslog.format`</a> | By default, logs are written using the Traefik Common Log Format (CLF).<br />Available formats: [`common`](#traefik-clf-format-fields) (Traefik extended CLF), [`genericCLF`](#generic-clf-format-fields) (standard CLF compatible with analyzers), or [`json`](#json-format-fields).<br />If the given format is unsupported, the default (`common`) is used instead. | "common" | No |
|
| <a id="opt-accesslog-format" href="#opt-accesslog-format" title="#opt-accesslog-format">`accesslog.format`</a> | By default, logs are written using the Traefik Common Log Format (CLF).<br />Available formats: [`common`](#traefik-clf-format-fields) (Traefik extended CLF), [`genericCLF`](#generic-clf-format-fields) (standard CLF compatible with analyzers), or [`json`](#json-format-fields).<br />If the given format is unsupported, the default (`common`) is used instead. | "common" | No |
|
||||||
| <a id="opt-accesslog-bufferingSize" href="#opt-accesslog-bufferingSize" title="#opt-accesslog-bufferingSize">`accesslog.bufferingSize`</a> | To write the logs in an asynchronous fashion, specify a `bufferingSize` option.<br />This option represents the number of log lines Traefik will keep in memory before writing them to the selected output.<br />In some cases, this option can greatly help performances.| 0 | No |
|
| <a id="opt-accesslog-bufferingSize" href="#opt-accesslog-bufferingSize" title="#opt-accesslog-bufferingSize">`accesslog.bufferingSize`</a> | To write the logs in an asynchronous fashion, specify a `bufferingSize` option.<br />This option represents the number of log lines Traefik will keep in memory before writing them to the selected output.<br />In some cases, this option can greatly help performances.| 0 | No |
|
||||||
| <a id="opt-accesslog-addInternals" href="#opt-accesslog-addInternals" title="#opt-accesslog-addInternals">`accesslog.addInternals`</a> | Enables access logs for internal resources (e.g.: `ping@internal`). | false | No |
|
| <a id="opt-accesslog-addInternals" href="#opt-accesslog-addInternals" title="#opt-accesslog-addInternals">`accesslog.addInternals`</a> | Enables access logs for internal resources (e.g.: `ping@internal`). | false | No |
|
||||||
@@ -252,6 +257,8 @@ experimental:
|
|||||||
otlpLogs: true
|
otlpLogs: true
|
||||||
|
|
||||||
accesslog:
|
accesslog:
|
||||||
|
# Keep Stdio logs alongside OTEL logging
|
||||||
|
dualOutput: true
|
||||||
otlp:
|
otlp:
|
||||||
http:
|
http:
|
||||||
endpoint: https://collector:4318/v1/logs
|
endpoint: https://collector:4318/v1/logs
|
||||||
@@ -263,6 +270,9 @@ accesslog:
|
|||||||
[experimental]
|
[experimental]
|
||||||
otlpLogs = true
|
otlpLogs = true
|
||||||
|
|
||||||
|
[accessLog]
|
||||||
|
dualOutput = true
|
||||||
|
|
||||||
[accesslog.otlp]
|
[accesslog.otlp]
|
||||||
http.endpoint = "https://collector:4318/v1/logs"
|
http.endpoint = "https://collector:4318/v1/logs"
|
||||||
http.headers.Authorization = "Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL"
|
http.headers.Authorization = "Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL"
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ func NewHandler(ctx context.Context, config *otypes.AccessLog) (*Handler, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Hooks.Add(otellogrus.NewHook("traefik", otellogrus.WithLoggerProvider(otelLoggerProvider)))
|
logger.Hooks.Add(otellogrus.NewHook("traefik", otellogrus.WithLoggerProvider(otelLoggerProvider)))
|
||||||
logger.Out = io.Discard
|
if !config.DualOutput {
|
||||||
|
logger.Out = io.Discard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform header names to a canonical form, to be used as is without further transformations,
|
// Transform header names to a canonical form, to be used as is without further transformations,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/alice"
|
"github.com/containous/alice"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
@@ -56,41 +57,99 @@ var (
|
|||||||
testStart = time.Now()
|
testStart = time.Now()
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOTelAccessLogWithBody(t *testing.T) {
|
func TestOTelAccessLogWithBodyAndDualOutput(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
format string
|
format string
|
||||||
bodyCheckFn func(*testing.T, string)
|
filePath string
|
||||||
|
dualOutput bool
|
||||||
|
bodyCheckFn func(*testing.T, string)
|
||||||
|
outLoggerCheckFn func(*testing.T, *logrus.Logger)
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Common format with log body",
|
desc: "Common format with log body",
|
||||||
format: CommonFormat,
|
format: CommonFormat,
|
||||||
|
filePath: "",
|
||||||
|
dualOutput: false,
|
||||||
bodyCheckFn: func(t *testing.T, log string) {
|
bodyCheckFn: func(t *testing.T, log string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// For common format, verify the body contains the Traefik common log formatted string
|
// For common format, verify the body contains the Traefik common log formatted string
|
||||||
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*[0-9]+ms.*"}`, log)
|
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*[0-9]+ms.*"}`, log)
|
||||||
},
|
},
|
||||||
|
outLoggerCheckFn: func(t *testing.T, l *logrus.Logger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, l.Out, io.Discard)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Generic CLF format with log body",
|
desc: "Generic CLF format with log body",
|
||||||
format: GenericCLFFormat,
|
format: GenericCLFFormat,
|
||||||
|
filePath: "",
|
||||||
|
dualOutput: false,
|
||||||
bodyCheckFn: func(t *testing.T, log string) {
|
bodyCheckFn: func(t *testing.T, log string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// For generic CLF format, verify the body contains the CLF formatted string
|
// For generic CLF format, verify the body contains the CLF formatted string
|
||||||
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*"}`, log)
|
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*"}`, log)
|
||||||
},
|
},
|
||||||
|
outLoggerCheckFn: func(t *testing.T, l *logrus.Logger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, l.Out, io.Discard)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "JSON format with log body",
|
desc: "JSON format with log body",
|
||||||
format: JSONFormat,
|
format: JSONFormat,
|
||||||
|
filePath: "",
|
||||||
|
dualOutput: false,
|
||||||
bodyCheckFn: func(t *testing.T, log string) {
|
bodyCheckFn: func(t *testing.T, log string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// For JSON format, verify the body contains the JSON formatted string
|
// For JSON format, verify the body contains the JSON formatted string
|
||||||
assert.Regexp(t, `"body":{"stringValue":".*DownstreamStatus.*:200.*"}`, log)
|
assert.Regexp(t, `"body":{"stringValue":".*DownstreamStatus.*:200.*"}`, log)
|
||||||
},
|
},
|
||||||
|
outLoggerCheckFn: func(t *testing.T, l *logrus.Logger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.Equal(t, l.Out, io.Discard)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Common format with log body and Dual Output (STDOUT + OTEL)",
|
||||||
|
format: CommonFormat,
|
||||||
|
filePath: "",
|
||||||
|
dualOutput: true,
|
||||||
|
bodyCheckFn: func(t *testing.T, log string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// For common format, verify the body contains the Traefik common log formatted string
|
||||||
|
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*[0-9]+ms.*"}`, log)
|
||||||
|
},
|
||||||
|
outLoggerCheckFn: func(t *testing.T, l *logrus.Logger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.NotEqual(t, l.Out, io.Discard)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Common format with log body and Dual Output (File logging + OTEL)",
|
||||||
|
format: CommonFormat,
|
||||||
|
filePath: filepath.Join(t.TempDir(), "traefik.log"),
|
||||||
|
dualOutput: true,
|
||||||
|
bodyCheckFn: func(t *testing.T, log string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// For common format, verify the body contains the Traefik common log formatted string
|
||||||
|
assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*[0-9]+ms.*"}`, log)
|
||||||
|
},
|
||||||
|
outLoggerCheckFn: func(t *testing.T, l *logrus.Logger) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
assert.NotEqual(t, l.Out, io.Discard)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +177,9 @@ func TestOTelAccessLogWithBody(t *testing.T) {
|
|||||||
t.Cleanup(collector.Close)
|
t.Cleanup(collector.Close)
|
||||||
|
|
||||||
config := &otypes.AccessLog{
|
config := &otypes.AccessLog{
|
||||||
Format: test.format,
|
Format: test.format,
|
||||||
|
DualOutput: test.dualOutput,
|
||||||
|
FilePath: test.filePath,
|
||||||
OTLP: &otypes.OTelLog{
|
OTLP: &otypes.OTelLog{
|
||||||
ServiceName: "test",
|
ServiceName: "test",
|
||||||
ResourceAttributes: map[string]string{"resource": "attribute"},
|
ResourceAttributes: map[string]string{"resource": "attribute"},
|
||||||
@@ -179,6 +240,9 @@ func TestOTelAccessLogWithBody(t *testing.T) {
|
|||||||
|
|
||||||
// Run format-specific body checks
|
// Run format-specific body checks
|
||||||
test.bodyCheckFn(t, log)
|
test.bodyCheckFn(t, log)
|
||||||
|
|
||||||
|
// Run OUT logger checks
|
||||||
|
test.outLoggerCheckFn(t, logHandler.logger)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ type AccessLog struct {
|
|||||||
Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"`
|
Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"`
|
||||||
BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"`
|
BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"`
|
||||||
AddInternals bool `description:"Enables access log for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
|
AddInternals bool `description:"Enables access log for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
|
||||||
|
DualOutput bool `description:"Enables access log output alongside OTLP. By default, this output is disabled when OTLP is configured." json:"dualOutput,omitempty" toml:"dualOutput,omitempty" yaml:"dualOutput,omitempty" export:"true"`
|
||||||
|
|
||||||
OTLP *OTelLog `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
OTLP *OTelLog `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user