Nginxログ形式コンパイラの実装詳細

src\http\modules\ngx_http_log_module.c

static char *
ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
    ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)
{
    u_char              *data, *p, ch;
    size_t               i, len;
    ngx_str_t           *value, var;
    ngx_int_t           *flush;
    ngx_uint_t           bracket, escape;
    ngx_http_log_op_t   *op;
    ngx_http_log_var_t  *v;

    escape = NGX_HTTP_LOG_ESCAPE_DEFAULT;
    value = args->elts;

    if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
        data = value[s].data + 7;

        if (ngx_strcmp(data, "json") == 0) {
            escape = NGX_HTTP_LOG_ESCAPE_JSON;

        } else if (ngx_strcmp(data, "none") == 0) {
            escape = NGX_HTTP_LOG_ESCAPE_NONE;

        } else if (ngx_strcmp(data, "default") != 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "不明なログ形式のエスケープ方式 \"%s\"", data);
            return NGX_CONF_ERROR;
        }

        s++;
    }

    for ( /* void */ ; s < args->nelts; s++) {

        i = 0;

        while (i < value[s].len) {

            op = ngx_array_push(ops);
            if (op == NULL) {
                return NGX_CONF_ERROR;
            }

            data = &value[s].data[i];

            if (value[s].data[i] == '$') {

                if (++i == value[s].len) {
                    goto invalid;
                }

                if (value[s].data[i] == '{') {
                    bracket = 1;

                    if (++i == value[s].len) {
                        goto invalid;
                    }

                    var.data = &value[s].data[i];

                } else {
                    bracket = 0;
                    var.data = &value[s].data[i];
                }

                for (var.len = 0; i < value[s].len; i++, var.len++) {
                    ch = value[s].data[i];

                    if (ch == '}' && bracket) {
                        i++;
                        bracket = 0;
                        break;
                    }

                    if ((ch >= 'A' && ch <= 'Z')
                        || (ch >= 'a' && ch <= 'z')
                        || (ch >= '0' && ch <= '9')
                        || ch == '_')
                    {
                        continue;
                    }

                    break;
                }

                if (bracket) {
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "\"%V\" 変数の閉じ括弧がありません", &var);
                    return NGX_CONF_ERROR;
                }

                if (var.len == 0) {
                    goto invalid;
                }

                for (v = ngx_http_log_vars; v->name.len; v++) {

                    if (v->name.len == var.len
                        && ngx_strncmp(v->name.data, var.data, var.len) == 0)
                    {
                        op->len = v->len;
                        op->getlen = NULL;
                        op->run = v->run;
                        op->data = 0;

                        goto found;
                    }
                }

                if (ngx_http_log_variable_compile(cf, op, &var, escape)
                    != NGX_OK)
                {
                    return NGX_CONF_ERROR;
                }

                if (flushes) {

                    flush = ngx_array_push(flushes);
                    if (flush == NULL) {
                        return NGX_CONF_ERROR;
                    }

                    *flush = op->data; /* 変数インデックス */
                }

            found:

                continue;
            }

            i++;

            while (i < value[s].len && value[s].data[i] != '$') {
                i++;
            }

            len = &value[s].data[i] - data;

            if (len) {

                op->len = len;
                op->getlen = NULL;

                if (len <= sizeof(uintptr_t)) {
                    op->run = ngx_http_log_copy_short;
                    op->data = 0;

                    while (len--) {
                        op->data <<= 8;
                        op->data |= data[len];
                    }

                } else {
                    op->run = ngx_http_log_copy_long;

                    p = ngx_pnalloc(cf->pool, len);
                    if (p == NULL) {
                        return NGX_CONF_ERROR;
                    }

                    ngx_memcpy(p, data, len);
                    op->data = (uintptr_t) p;
                }
            }
        }
    }

    return NGX_CONF_OK;

invalid:

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "無効なパラメータ \"%s\"", data);

    return NGX_CONF_ERROR;
}

この関数はNginxのログ形式文字列をコンパイルするためのものです。log_formatディレクティブの引数を一連の操作命令に変換し、ログ記録時に効率的に実行できるようにします。

関数シグネチャ

static char *
ngx_http_log_compile_format(ngx_conf_t *cf, ngx_array_t *flushes,
    ngx_array_t *ops, ngx_array_t *args, ngx_uint_t s)

パラメータの説明:

  • cf: 設定構造体へのポインタ。設定関連情報とメモリプールを含みます
  • flushes: フラッシュする変数インデックスを格納する配列
  • ops: 生成された操作命令を格納する配列
  • args: ログ形式のパラメータを含む配列
  • s: 開始パラメータインデックス

変数宣言部分

u_char              *data, *p, ch;
size_t               i, len;
ngx_str_t           *value, var;
ngx_int_t           *flush;
ngx_uint_t           bracket, escape;
ngx_http_log_op_t   *op;
ngx_http_log_var_t  *v;
  • data, p, ch: 文字列処理のための一時ポインタと文字
  • i, len: ループインデックスと長さ変数
  • value: パラメータ配列要素へのポインタ
  • var: 解析された変数名を格納
  • flush: flushes配列を操作するためのポインタ
  • bracket: ${}構文を使用するかどうかのフラグ
  • escape: エスケープ方式のフラグ
  • op: 現在の操作命令へのポインタ
  • v: 事前定義された変数を走査するためのポインタ

初期化部分

escape = NGX_HTTP_LOG_ESCAPE_DEFAULT;
value = args->elts;
  • デフォルトのエスケープ方式をNGX_HTTP_LOG_ESCAPE_DEFAULTに設定
  • パラメータ配列の要素ポインタを取得

escapeパラメータの処理

if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) {
    data = value[s].data + 7;

    if (ngx_strcmp(data, "json") == 0) {
        escape = NGX_HTTP_LOG_ESCAPE_JSON;

    } else if (ngx_strcmp(data, "none") == 0) {
        escape = NGX_HTTP_LOG_ESCAPE_NONE;

    } else if (ngx_strcmp(data, "default") != 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "不明なログ形式のエスケープ方式 \"%s\"", data);
        return NGX_CONF_ERROR;
    }

    s++;
}
  • escape=パラメータがあるかチェック
  • エスケープ方式を解析して設定:jsonnone、またはdefault
  • 不明なエスケープ方式の場合はエラーを返す
  • パラメータインデックスsを増加

各パラメータを処理するメインループ

for ( /* void */ ; s < args->nelts; s++) {
    i = 0;
    while (i < value[s].len) {
  • インデックスsからすべてのパラメータを走査
  • 各パラメータの位置0から文字ごとに処理

変数処理部分

op = ngx_array_push(ops);
if (op == NULL) {
    return NGX_CONF_ERROR;
}

data = &value[s].data[i];

if (value[s].data[i] == '$') {
  • ops配列に新しい操作用の領域を割り当て
  • 割り当てが成功したかチェック
  • 現在位置のポインタを取得
  • $記号が見つかった場合、変数処理を開始

変数名の処理

if (++i == value[s].len) {
    goto invalid;
}

if (value[s].data[i] == '{') {
    bracket = 1;
    if (++i == value[s].len) {
        goto invalid;
    }
    var.data = &value[s].data[i];
} else {
    bracket = 0;
    var.data = &value[s].data[i];
}
  • $の次の文字に進み、範囲チェック
  • {が続く場合は${}構文として扱い、対応するフラグを設定
  • それ以外の場合は通常の変数構文として扱う

変数名の抽出

for (var.len = 0; i < value[s].len; i++, var.len++) {
    ch = value[s].data[i];

    if (ch == '}' && bracket) {
        i++;
        bracket = 0;
        break;
    }

    if ((ch >= 'A' && ch <= 'Z')
        || (ch >= 'a' && ch <= 'z')
        || (ch >= '0' && ch <= '9')
        || ch == '_')
    {
        continue;
    }

    break;
}
  • 変数名の長さを0から始めてカウント
  • }が見つかって${}構文の場合はループを終了
  • 変数名として有効な文字(英数字、アンダースコア)かチェック
  • 無効な文字が見つかったらループを終了

変数名の検証

if (bracket) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "\"%V\" 変数の閉じ括弧がありません", &var);
    return NGX_CONF_ERROR;
}

if (var.len == 0) {
    goto invalid;
}
  • ${構文で対応する}がない場合エラー
  • 変数名の長さが0の場合エラー

事前定義された変数の検索

for (v = ngx_http_log_vars; v->name.len; v++) {
    if (v->name.len == var.len
        && ngx_strncmp(v->name.data, var.data, var.len) == 0)
    {
        op->len = v->len;
        op->getlen = NULL;
        op->run = v->run;
        op->data = 0;
        goto found;
    }
}
  • ngx_http_log_varsテーブルを走査
  • 一致する変数が見つかったら操作命令を設定してfoundにジャンプ

変数のコンパイル

if (ngx_http_log_variable_compile(cf, op, &var, escape) != NGX_OK) {
    return NGX_CONF_ERROR;
}

if (flushes) {
    flush = ngx_array_push(flushes);
    if (flush == NULL) {
        return NGX_CONF_ERROR;
    }
    *flush = op->data; /* 変数インデックス */
}
  • 事前定義されていない変数の場合はコンパイル
  • flushesが必要な場合は変数インデックスを追加

見つかった変数の処理

found:
    continue;
  • 次のループ反復に進む

通常テキストの処理

i++;

while (i < value[s].len && value[s].data[i] != '$') {
    i++;
}

len = &value[s].data[i] - data;

if (len) {
    op->len = len;
    op->getlen = NULL;

    if (len <= sizeof(uintptr_t)) {
        op->run = ngx_http_log_copy_short;
        op->data = 0;

        while (len--) {
            op->data <<= 8;
            op->data |= data[len];
        }

    } else {
        op->run = ngx_http_log_copy_long;
        p = ngx_pnalloc(cf->pool, len);
        if (p == NULL) {
            return NGX_CONF_ERROR;
        }
        ngx_memcpy(p, data, len);
        op->data = (uintptr_t) p;
    }
}
  • $ではない通常テキストの処理(2つの$の間の内容)
  • テキストの長さを計算
  • テキストが存在する場合、操作命令を設定
  • 短いテキストは直接op->dataに格納
  • 長いテキストはメモリを確保してポインタを格納

戻り値とエラー処理

return NGX_CONF_OK;

invalid:
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "無効なパラメータ \"%s\"", data);
    return NGX_CONF_ERROR;
  • 成功時はNGX_CONF_OKを返す
  • エラー時はログを出力してNGX_CONF_ERRORを返す

タグ: nginx C言語 ログモジュール コンパイラ パフォーマンス最適化

8月4日 12:02 投稿