pydantic / logfire

Uncomplicated Observability for Python and beyond! 🪵🔥
https://docs.pydantic.dev/logfire/
MIT License
1.9k stars 54 forks source link

TypeError: bad argument type for built-in operation when custom exception raised #176

Closed lironesamoun closed 3 months ago

lironesamoun commented 3 months ago

Description

On my code, I raise a custom exception if a condition is not met.

        if len(jobs) <= self.number_minimum_jobs
            raise JobsNumberMinimumException(f"not enough jobs: {len(jobs)} over "
                                                     f"the requirements needed: {self.number_minimum_jobs}")

When I run my code, the exception is raised and I got an error from logfire.

not enough jobs: 20 over the requirements needed: 50 Invalid type JobsNumberMinimumException for attribute 'logfire.msg_template' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types

Following by this

/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/file.py:58: WritingFallbackWarning: Failed to export spans, writing to fallback file: /Users/user/Documents/dev/project/data/.logfire/logfire_spans.bin
  warnings.warn(
Exception while exporting Span batch.
Traceback (most recent call last):
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/fallback.py", line 20, in export
    res = self.exporter.export(spans)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/otlp.py", line 56, in export
    return super().export(spans)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/wrapper.py", line 14, in export
    return self.wrapped_exporter.export(spans)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py", line 136, in export
    serialized_data = encode_spans(spans).SerializeToString()
                      ^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 58, in encode_spans
    resource_spans=_encode_resource_spans(sdk_spans)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 81, in _encode_resource_spans
    pb2_span = _encode_span(sdk_span)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 109, in _encode_span
    return PB2SPan(
           ^^^^^^^^
TypeError: bad argument type for built-in operation

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/sdk/trace/export/__init__.py", line 367, in _export_batch
    self.span_exporter.export(self.spans_list[:idx])  # type: ignore
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/remove_pending.py", line 45, in export
    return super().export(result)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/wrapper.py", line 14, in export
    return self.wrapped_exporter.export(spans)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/fallback.py", line 22, in export
    self.fallback.export(spans)
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/logfire/_internal/exporters/file.py", line 68, in export
    encoded_spans = encode_spans(spans)
                    ^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 58, in encode_spans
    resource_spans=_encode_resource_spans(sdk_spans)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 81, in _encode_resource_spans
    pb2_span = _encode_span(sdk_span)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/.pyenv/versions/3.11.2/envs/project-data-3.11/lib/python3.11/site-packages/opentelemetry/exporter/otlp/proto/common/_internal/trace_encoder/__init__.py", line 109, in _encode_span
    return PB2SPan(
           ^^^^^^^^
TypeError: bad argument type for built-in operation

And on the "Live" Section, I don't see any exception or things related to this.

Any insights ?

Python, Logfire & OS Versions, related packages (not required)

logfire="0.31.0"
platform="macOS-14.4.1-arm64-arm-64bit"
python="3.11.2 (main, Feb 24 2023, 14:47:46) [Clang 14.0.0 (clang-1400.0.29.202)]"
[related_packages]
requests="2.31.0"
pydantic="1.10.13"
fastapi="0.109.2"
openai="1.17.0"
protobuf="4.25.3"
rich="13.7.0"
opentelemetry-api="1.24.0"
opentelemetry-exporter-otlp-proto-common="1.24.0"
opentelemetry-exporter-otlp-proto-http="1.24.0"
opentelemetry-instrumentation="0.45b0"
opentelemetry-instrumentation-asgi="0.45b0"
opentelemetry-instrumentation-asyncpg="0.45b0"
opentelemetry-instrumentation-dbapi="0.45b0"
opentelemetry-instrumentation-fastapi="0.45b0"
opentelemetry-instrumentation-grpc="0.45b0"
opentelemetry-instrumentation-httpx="0.45b0"
opentelemetry-instrumentation-jinja2="0.45b0"
opentelemetry-instrumentation-mysql="0.45b0"
opentelemetry-instrumentation-pymysql="0.45b0"
opentelemetry-instrumentation-requests="0.45b0"
opentelemetry-instrumentation-sqlalchemy="0.45b0"
opentelemetry-instrumentation-sqlite3="0.45b0"
opentelemetry-instrumentation-tornado="0.45b0"
opentelemetry-instrumentation-urllib="0.45b0"
opentelemetry-instrumentation-urllib3="0.45b0"
opentelemetry-proto="1.24.0"
opentelemetry-sdk="1.24.0"
opentelemetry-semantic-conventions="0.45b0"
opentelemetry-util-http="0.45b0"
alexmojaki commented 3 months ago

It sounds like you wrote code like this:

try:
    ...
except Exception as e:
    logfire.exception(e)

logfire.msg_template refers to that first argument of logfire.exception, it has to be a string. You can write logfire.exception("Failed to do jobs") and the exception itself will be picked up automatically.

lironesamoun commented 3 months ago

Actually, I don't use logfire.exception(e) unless I'm mistaken.


class JobsNumberMinimumException(DataQualityException):
    def __init__(self, message=""):
        super().__init__(message)

class DataQualityException(Exception):
    """
    Base exception for data quality issues.
    """

    def __init__(self, message: str):
        self.message = message
        super().__init__(self.message)

if len(jobs) <= self.number_minimum_jobs
            raise JobsNumberMinimumException(f"not enough jobs: {len(jobs)} over "
                                                     f"the requirements needed: {self.number_minimum_jobs}")

Inside JobsNumberMinimumException, it's a string.

alexmojaki commented 3 months ago

But where's the code where you used logfire?

lironesamoun commented 3 months ago

So far, I've juste integrated Logfire with the standard library logging module (logging). It works fine for most of my process but on this case it works until I get to the exception level, the code above.

alexmojaki commented 3 months ago

OK, this makes sense. There must be some code doing this:

import logging
logger = logging.getLogger(...)

try:
    ...
except Exception as e:
    logger.<log method>(e)

This is a bug that we should fix ASAP by converting log_record.msg to a string in the logging integration. In the meantime, if you can identify those logging calls, you can ensure that they receive a string instead to resolve the error.

alexmojaki commented 3 months ago

In fact it should be noted that even after the fix it would be best to ensure that you pass a string where possible describing the general problem. Otherwise it will be hard when viewing/querying logs to group together all logs coming from this location if they have different error messages, and it will also be hard to distinguish between similar exceptions being logged from different places.

lironesamoun commented 3 months ago

I'll take a look but I don't think that I have any logger.(e) inside my except. That's why I'm confused especially for my case where I don't see one.

Thank for your recommendation.

Do you think that this string as an example is a bad practice: _f"not enough jobs: {len(jobs)} over the requirements needed: {self.number_minimumjobs}" ?

alexmojaki commented 3 months ago

I'll take a look but I don't think that I have any logger.(e) inside my except. That's why I'm confused especially for my case where I don't see one.

It's probably inside a library somewhere.

Do you think that this string as an example is a bad practice: _f"not enough jobs: {len(jobs)} over the requirements needed: {self.number_minimumjobs}" ?

No it's not bad practice, but also it makes no difference to logfire.

alexmojaki commented 3 months ago

New version of the SDK should fix this.