90195
# 環境

  * Python 3.10.2

# やりたいこと

ログメッセージに、数値を出力したいです。  
数値は桁区切りのカンマも出力したいです。

```

    In [27]: import logging
        ...: logging_formatter = '%(levelname)-8s : %(asctime)s : %(name)s : %(message)s'
        ...: logging.basicConfig(format=logging_formatter)
        ...: 
    
    In [29]: i = 123456789 
    
    In [30]: logger.error(f"{i:,} byte")
    ERROR    : 2022-07-23 00:04:48,683 : __main__ : 1,234,567,789 byte
    
```

以下の記事によると、f-stringを使ってログメッセージを出力するのは、ログメッセージの集約の観点でよろしくありません。

[ログメッセージをフォーマットしてロガーに渡さない](https://jisou-
programmer.beproud.jp/%E3%83%AD%E3%82%AE%E3%83%B3%E3%82%B0/69-%E3%83%AD%E3%82%B0%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%97%E3%81%A6%E3%83%AD%E3%82%AC%E3%83%BC%E3%81%AB%E6%B8%A1%E3%81%95%E3%81%AA%E3%81%84.html)

したがって、次のようなコードでログメッセージを出力したいです。

```

    In [31]: logger.error("%d byte", i)
    ERROR    : 2022-07-23 00:09:48,531 : __main__ : 1234567789 byte
    
```

ただし、上記のコードで桁区切りのカンマを表示する方法が分かりませんでした。

# 質問

`logger.error("%d byte", i)`のようなコードで、桁区切りのカンマを表示するには、どのように指定すればよいでしょうか？  
以下のサイトを眺めましたが、分かりませんでした。  
<https://docs.python.org/ja/3/library/logging.html#logging.Formatter>
CC BY-SA 4.0
2022-07-22T06:03:46.003
90141
2022-07-26T04:30:24.110
2022-07-22T15:11:39.943
19524
19524
question
python python3
ログメッセージをフォーマットせずに出力したいです。桁区切りのカンマを表示するには、どのように指定すればよいでしょうか？
% によるフォーマッティングで3桁区切りさせる方法はないと思いますよ。  
3桁区切りにすることが必要ならば、数値を3桁区切りの文字列にしてからロガーに渡せばどうでしょうか。

```

    logger.error("%s bytes", f"{i:,d}")
    
```
CC BY-SA 4.0
2022-07-23T00:22:59.050
90154
2022-07-23T00:22:59.050
9464
90141
answer
(docs.python.org)
[フォーマッタオブジェクト](https://docs.python.org/ja/3/library/logging.html#formatter-
objects) より

> この書式文字列は、 Python 標準の `%` を使った変換文字列で構成されます。文字列整形に関する詳細は [printf
> 形式の文字列書式化](https://docs.python.org/ja/3/library/stdtypes.html#old-string-
> formatting) を参照してください。

書式文字列を見れば分かる通り, カンマ区切りは存在しない  
しかし `str.format()` を利用した Factoryを用意すれば, 使えないこともない

```

    class BraceMesg(logging.LogRecord):
        def getMessage(self):
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
    
    logging.basicConfig(format='[%(asctime)s] %(message)s')
    logging.setLogRecordFactory(BraceMesg)
    logging.error('num {:,}', 1024)
    logging.error('Say {} {}', 'hello', 'world')
    
```

**※ただし**

[Logging クックブック: 別の format
スタイルを利用する](https://docs.python.org/ja/3/howto/logging-
cookbook.html?highlight=setlogrecordfactory#use-of-alternative-formatting-
styles) より

> logging が Python 標準ライブラリに追加された時、メッセージを動的な内容でフォーマットする唯一の方法は `%`
> を使ったフォーマットでした。

「固有の書式化スタイルをアプリケーション全体で使う」より

> logging の呼び出し (debug(), info() など)
> は、ログメッセージのために位置引数しか受け取らず、キーワード引数はそれを処理するときのオプションを指定するためだけに使われます。  
>  logging パッケージは内部で % を使って format 文字列と引数をマージしているので、 str.format() や
> string.Template を使って logging を呼び出す事はできません。既存の logging 呼び出しは %-format
> を使っているので、後方互換性のためにこの部分を変更することはできません。  
>
> 特定のロガーに関連付ける書式スタイルへの提案がなされてきましたが、そのアプローチは同時に後方互換性の問題にぶち当たります。あらゆる既存のコードはロガーの名前を使っているでしょうし、
> % 形式書式化を使っているでしょう。  
>
> あらゆるサードパーティのライブラリ、あらゆるあなたのコードの間で相互運用可能なようにロギングを行うには、書式化についての決定は、個々のログ呼び出しのレベルで行う必要があります。これは受け容れ可能な代替書式化スタイルに様々な手段の可能性を広げます。

「LogRecord ファクトリを使う」より

> Python 3.2 において、上述した Formatter の変更とともに、 setLogRecordFactory() 関数を使って
> LogRecord のサブクラスをユーザに指定することを可能にするロギングパッケージの機能拡張がありました。これにより、 getMessage()
> をオーバライドして た だ し い ことをする、あなた自身の手による LogRecord
> のサブクラスをセットすることが出来ます。このメソッドの実装は基底クラスでは msg % args
> 書式化をし、あなたの代替の書式化の置換が出来る場所ですが、他のコードとの相互運用性を保障するために、全ての書式化スタイルをサポートするよう注意深く行うべきであり、また、%-書式化をデフォルトで認めるべきです。

・・・ ということなので, 実際のコードはもっと複雑になるかも

結局のところ「カスタムなメッセージオブジェクトを使う」にあるように, 以下のような指定を行うとか,

```

    >>> __ = BraceMessage
    >>> print(__('Message with {0} {1}', 2, 'placeholders'))
    
```

あるいは f-string や formatを使ったほうがよいでしょう

```

    >>> format(1024, ',')
    '1,024'
    >>> logging.error('num %s', format(1024, ','))
    
```
CC BY-SA 4.0
2022-07-23T15:33:57.563
90166
2022-07-23T15:33:57.563
43025
90141
answer
フォーマットせずに渡すけど、フォーマットしたい、という矛盾した要求に思えますが、ニーズは分かります。

[ログメッセージをフォーマットしてロガーに渡さない](https://jisou-
programmer.beproud.jp/%E3%83%AD%E3%82%AE%E3%83%B3%E3%82%B0/69-%E3%83%AD%E3%82%B0%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%97%E3%81%A6%E3%83%AD%E3%82%AC%E3%83%BC%E3%81%AB%E6%B8%A1%E3%81%95%E3%81%AA%E3%81%84.html)
のが良いのは、loggerの第一引数（メッセージ部分）をキーにログ出力を集約することがあるためです。言い換えると、第二引数以降にフォーマットした数値（の文字列）を渡してしまえば良いと思います。

```

    >>> import logging
    >>> logger = logging.getLogger()
    >>> value = 12345678
    >>> logger.error('size %s mb', value)
    size 12345678 mb
    >>> logger.error('size %s mb', f'{value:,}')
    size 12,345,678 mb
    
```

これで、メッセージ部分は `'size %s mb'` で一意なキーには変わりないので、目的は達成できると思います。  
もし、ログ表示とは別に裏で生データも扱えるようにしておきたいのであれば、extraで元データも渡しておく方法があります。

```

    >>> logger.error('size %s mb', f'{value:,}', extra={'value': value})
    size 12,345,678 mb
    
```

値を2つ渡すのが面倒、表示は3桁区切りにしたいが生データを維持したい、ということであれば、ロギング用の3桁区切りラッパークラスを実装してしまう方法もあります。

```

    >>> class ThreeDigit(int):
    ...   def __str__(self):
    ...     return f'{self:,}'
    ... 
    >>> ThreeDigit(value)
    12345678
    >>> print(ThreeDigit(value))
    12,345,678
    >>> logger.error('size %s mb', ThreeDigit(value))
    size 12,345,678 mb
    
```

いずれの方法でも実現はできますが、JSONの構造化ログでログデータを扱い始めると、Python
loggerのレイヤーで3桁区切りのフォーマットをする必要がなくなる（構造化ログのビューワーを別途用意すると思われる）ため、将来どうしていきたいか次第で選べば良さそうです。

ログ出力はコンソールに表示するだけで収集しない、ということであれば、（良いプラクティスではありませんが）気にせずフォーマットしてしまうという割り切りもありだと思います。
CC BY-SA 4.0
2022-07-26T04:30:24.110
90195
2022-07-26T04:30:24.110
806
90141
answer