在Docker环境下将PHP-FPM和Nginx访问日志格式化为标准化的JSON字符串(附实例)

854 阅读2分钟

如果你想把PHP-FPM和Nginx容器日志格式改为JSON,同时保持相同的风格,你可以使用下面的例子。

结构

.
├── docker
│   ├── docker-compose.yml
│   ├── Makefile
│   ├── nginx
│   │   ├── app.conf
│   │   ├── Dockerfile
│   │   └── nginx.conf
│   └── php
│       ├── Dockerfile
│       ├── php.ini
│       └── www.conf
└── index.php

文件

docker/docker-compose.yml

version: "3.4"

services:

  wait_php:
    build:
      context: "./php"
    hostname: "wait-php"
    volumes:
      - "..:/app"
    environment:
      PS1: "\\u@\\h:\\w\\$$ "

  wait_nginx:
    build:
      context: "./nginx"
    hostname: "wait-nginx"
    ports:
      - "1080:80"
    volumes:
      - "..:/app"
    depends_on:
      - "wait_php"
    environment:
      PS1: "\\u@\\h:\\w\\$$ "

docker/nginx/Dockerfile

FROM nginx:1.15.8-alpine

WORKDIR /app

COPY app.conf /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/nginx.conf

docker/nginx/app.conf

我们必须为PHP-FPM日志添加fastcgi_param HTTP_X_REQUEST_ID $request_id;

server {
    listen 80 default_server;

    server_name localhost;

    root /app;

    index index.php;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass wait_php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTP_X_REQUEST_ID $request_id;
    }
}

docker/nginx/nginx.conf

如果你删除error_log off; ,与PHP相关的编码错误将由Nginx容器而不是PHP-FPM容器显示。我喜欢我的PHP-FPM容器拥有自己的错误,所以我保留它:

user nginx;

worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format json_combined escape=json
          '{'
            '"time_local":"$time_iso8601",'
            '"client_ip":"$http_x_forwarded_for",'
            '"remote_addr":"$remote_addr",'
            '"remote_user":"$remote_user",'
            '"request":"$request",'
            '"status":"$status",'
            '"body_bytes_sent":"$body_bytes_sent",'
            '"request_time":"$request_time",'
            '"http_referrer":"$http_referer",'
            '"http_user_agent":"$http_user_agent",'
            '"request_id":"$request_id"'
          '}';

    access_log /var/log/nginx/access.log json_combined;

    error_log off;

    sendfile on;

    keepalive_timeout 65;

    include /etc/nginx/conf.d/*.conf;
}

docker/php/Dockerfile

FROM php:7.2.13-fpm-alpine3.8

WORKDIR /app

COPY php.ini /usr/local/etc/php/conf.d/php.override.ini
COPY www.conf /usr/local/etc/php-fpm.d/www.conf

CMD ["php-fpm", "--nodaemonize"]

docker/php/php.ini

[PHP]
date.timezone=UTC
log_errors=On
error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors=Off
max_execution_time=60
memory_limit=256M

docker/php/www.conf

你可能需要添加clear_env=no ,看看它如何改变日志,或者是否有助于访问更多环境变量:

[global]
daemonize=no

[www]
user=www-data
group=www-data

listen=wait_nginx:9000

pm=dynamic
pm.max_children=40
pm.start_servers=2
pm.min_spare_servers=2
pm.max_spare_servers=4
pm.max_requests=500

access.format='{"time_local":"%{%Y-%m-%dT%H:%M:%S%z}T","client_ip":"%{HTTP_X_FORWARDED_FOR}e","remote_addr":"%R","remote_user":"%u","request":"%m %{REQUEST_URI}e %{SERVER_PROTOCOL}e","status":"%s","body_bytes_sent":"%l","request_time":"%d","http_referrer":"%{HTTP_REFERER}e","http_user_agent":"%{HTTP_USER_AGENT}e","request_id":"%{HTTP_X_REQUEST_ID}e"}'
$ docker ps
CONTAINER ID    IMAGE               COMMAND                  CREATED          STATUS          PORTS                  NAMES
96d7f6c16904    docker_wait_nginx   "nginx -g 'daemon of…"   4 minutes ago    Up 4 minutes    0.0.0.0:1080->80/tcp   docker_wait_nginx_1
f8d0c3367925    docker_wait_php     "docker-php-entrypoi…"   4 minutes ago    Up 4 minutes    9000/tcp               docker_wait_php_1

配置信息

PHP访问日志格式有C, d, e, f, l, m, M, n, o, p, P, q, Q, r, R, s, t, T, u 选项,它们与% 前缀一起使用 - 例如:%C 。然而,e (代表Nginx服务器fastcgi_param vars和PHP-FPM服务器$_SERVER vars)和o (代表 "头 "输出)有点不同,所以它们被用作%{VARIABLE_NAME}e - 例如:%{REQUEST_URI}e 。另外,Nginx配置文件的变量列表可以在这里找到。

PHP默认的访问日志格式"%R - %u %t \"%m %r\" %s" ,日志输出是172.22.0.3 - 26/May/2019:10:57:14 +0000 "GET /index.php" 200

为了演示,如果你使用了日志格式中的所有选项,你的日志将看起来像下面这样。

access.format = '{"C":"%C","d":"%d","f":"%f","l":"%l","m":"%m","M":"%M","n":"%n","P":"%P","p":"%p","q":"%q","Q":"%Q","r":"%r","R":"%R","s":"%s","T":"%T","t":"%t","u":"%u"}'
$ curl -i 0.0.0.0:1080

{
  "C": "0.00", # CPU usage
  "d": "5.001", # Request processing duration (5 sec 1 milsec)
  "f": "/app/index.php", # Script run
  "l": "0", # Content length
  "m": "GET", # Method
  "M": "2097152", # Memory usage (byte)
  "n": "www", # Pool name
  "P": "1", # PID (master process)
  "p": "6", # PID (child process - pm.start_servers)
  "q": "", # Query string
  "Q": "", # The '?' character if query string exists
  "r": "/index.php", # Script run
  "R": "172.22.0.3", # IP of requester (Nginx)
  "s": "200", # HTTP status code
  "T": "2019-06-12T20:26:05+00:00", # Response time
  "t": "2019-06-12T20:26:00+00:00", # Request time
  "u": "" # Authenticated/remote user
}

# YOU MUST READ "/usr/local/etc/php-fpm.d/www.conf" file for detailed explanation and better usage of these parameters

测试

PHP-FPM错误日志

wait_php_1    | [26-May-2019 17:49:02] WARNING: [pool www] child 7 said into stderr: "NOTICE: PHP message: PHP Parse error:  syntax error, unexpected end of file in /app/index.php on line 4"

wait_php_1    | [26-May-2019 17:49:49] WARNING: [pool www] child 8 said into stderr: "NOTICE: PHP message: PHP Fatal error:  Uncaught Error: Class 'Tomato' not found in /app/index.php:5"
wait_php_1    | [26-May-2019 17:49:49] WARNING: [pool www] child 8 said into stderr: "Stack trace:"
wait_php_1    | [26-May-2019 17:49:49] WARNING: [pool www] child 8 said into stderr: "#0 {main}"
wait_php_1    | [26-May-2019 17:49:49] WARNING: [pool www] child 8 said into stderr: "  thrown in /app/index.php on line 5"

访问日志

我已经尽力在配置中匹配这两种日志格式,但还是要靠你来改进:

# PHP-FPM - wait_php_1

{
  "time_local": "2019-06-12T20:26:01+00:00",
  "client_ip": "2.28.107.100",
  "remote_addr": "172.22.0.3",
  "remote_user": "",
  "request": "GET / HTTP/1.1",
  "status": "200",
  "body_bytes_sent": "0",
  "request_time": "0.002",
  "http_referrer": "-",
  "http_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
  "request_id": "98bb9fbfb3bc710422d87b22d380a4e6"
}
# Nginx - wait_nginx_1

{
  "time_local": "2019-06-12T20:26:00+00:00",
  "client_ip": "2.28.107.100",
  "remote_addr": "192.168.99.1",
  "remote_user": "",
  "request": "GET / HTTP/1.1",
  "status": "200",
  "body_bytes_sent": "2509",
  "request_time": "0.004",
  "http_referrer": "",
  "http_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
  "request_id": "98bb9fbfb3bc710422d87b22d380a4e6"
}