Traefik doesn’t just log access; it crafts a narrative of every request, a story you can parse to understand traffic flow and troubleshoot issues.

Let’s see this in action. Imagine a simple Traefik setup with two backend services, whoami-a and whoami-b, both running on port 80. Traefik is configured to listen on port 8000 and route to these services based on the hostname.

# traefik.yml
log:
  level: INFO # Essential for seeing log-related messages, though not strictly for access logs themselves.

accessLog:
  enabled: true
  format: json # We'll start with JSON for its structured nature.
  filePath: "/var/log/traefik/access.log" # Where the logs will live.
  buffering:
    maxMessages: 100 # Log messages are buffered before being written.
    maxAge: 10s # Flush buffer every 10 seconds.
    flushInterval: 5s # Or flush every 5 seconds, whichever comes first.

entryPoints:
  web:
    address: ":8000"

providers:
  docker:
    exposedByDefault: false

services:
  whoami-a:
    loadBalancer:
      servers:
        - url: "http://whoami-a:80"

  whoami-b:
    loadBalancer:
      servers:
        - url: "http://whoami-b:80"

routers:
  whoami-a-router:
    rule: "Host(`whoami-a.localhost`)"
    service: "whoami-a"
    entryPoints:
      - web

  whoami-b-router:
    rule: "Host(`whoami-b.localhost`)"
    service: "whoami-b"
    entryPoints:
      - web

Now, if we send a request to whoami-a.localhost:8000 using curl:

curl -H "Host: whoami-a.localhost" http://localhost:8000

The access.log file (located at /var/log/traefik/access.log) will contain an entry similar to this (in JSON format):

{
  "time": "2023-10-27T10:00:00.123Z",
  "level": "info",
  "msg": "",
  "routerName": "whoami-a-router",
  "routerNamespace": "",
  "serviceName": "whoami-a",
  "serviceNamespace": "",
  "remoteAddr": "192.168.1.100:54321",
  "remoteUser": "",
  "req": {
    "method": "GET",
    "path": "/",
    "protocol": "HTTP/1.1",
    "host": "whoami-a.localhost",
    "header": {
      "Host": [
        "whoami-a.localhost"
      ],
      "User-Agent": [
        "curl/7.74.0"
      ]
    }
  },
  "resp": {
    "statusCode": 200,
    "size": 873,
    "header": {
      "Content-Type": [
        "text/plain; charset=utf-8"
      ],
      "X-Content-Type-Options": [
        "nosniff"
      ]
    }
  },
  "duration": 5000000,
  "traceID": "a1b2c3d4e5f67890"
}

This structured output is incredibly powerful. It tells us exactly which router (whoami-a-router) handled the request, which service (whoami-a) it was ultimately sent to, the client’s IP address (remoteAddr), the full request details (req), the response status and size (resp), and how long the entire transaction took (duration). The traceID is crucial for distributed tracing if you have multiple services involved.

Traefik’s access logging is built around the accessLog configuration block. The core directive is enabled: true. Without this, no access logs will be generated. The filePath specifies where the log file will be written. If this path is not writable by the Traefik process, logs will fail to appear.

The format directive is where the magic of customization happens. By default, Traefik uses a compact, human-readable format. However, for programmatic analysis, structured formats like json are far superior. You can also define custom formats using a template syntax. For example, to create a Common Log Format (CLF) style log:

accessLog:
  enabled: true

  format: "{{.Client.IP}} - {{.Client.User}} [{{.StartDate | date \"02/Jan/2006:15:04:05 -0700\"}}] \"{{.Request.Method}} {{.Request.RequestURI}} {{.Request.Protocol}}\" {{.Response.StatusCode}} {{.Response.Size}} \"{{.Request.Referer}}\" \"{{.Request.UserAgent}}\""

  filePath: "/var/log/traefik/access.log"

This custom format explicitly pulls out fields like client IP, request method, URI, protocol, status code, response size, referer, and user agent, arranging them in the CLF structure. The {{.StartDate | date \"02/Jan/2006:15:04:05 -0700\"}} part is a Go template function that formats the timestamp into the standard CLF format.

The buffering section allows Traefik to batch log entries before writing them to disk. This can significantly improve performance, especially under heavy load, by reducing the number of disk I/O operations. maxMessages sets the maximum number of log entries to buffer, maxAge sets the maximum age of a buffered entry before it’s flushed, and flushInterval specifies how often the buffer is flushed. Using these settings effectively can prevent log-related performance bottlenecks.

Beyond the basic format, Traefik offers a rich set of fields you can include in your logs. These are accessible via the Go template syntax. Some common ones include:

  • {{.RouterName}}: The name of the router that matched the request.

  • {{.ServiceName}}: The name of the service the request was forwarded to.

  • {{.Request.Host}}: The host header from the incoming request.

  • {{.Request.UserAgent}}: The User-Agent header.

  • {{.Response.StatusCode}}: The HTTP status code of the response.

  • {{.Duration}}: The time taken for the request in nanoseconds.

  • {{.TraceID}}: The distributed tracing ID.

When configuring custom formats, the most common pitfall is incorrect Go template syntax, especially with date formatting or field access. Double-checking the field names against Traefik’s documentation is essential. Another common issue is ensuring the filePath is correctly set and that Traefik has write permissions to that directory. For JSON logs, ensuring your log aggregation system can correctly parse the JSON is key.

The true power of Traefik’s access logs lies in their ability to be easily integrated with log analysis tools like Elasticsearch, Splunk, or Grafana Loki. By outputting in JSON, you provide a machine-readable stream that these tools can ingest, index, and query, allowing for sophisticated analysis of traffic patterns, error rates, and performance metrics.

The buffering configuration, while beneficial for performance, introduces a slight delay between a request being processed and its log entry appearing on disk. This means that in the immediate aftermath of a system crash, a small number of recent requests might not be fully logged.

The next step in understanding your traffic flow is often diving into Traefik’s metrics, which provide real-time insights into request volumes, error rates, and latency.

Want structured learning?

Take the full Traefik course →