142142}
143143
144144
145+ class _LogTemplateParameters (dict [str , object ]):
146+ """Preserve placeholders whose values were not provided."""
147+
148+ def __missing__ (self , key : str ) -> str :
149+ return "{" + key + "}"
150+
151+
145152@dataclasses .dataclass
146153class Logs3OverflowInputRow :
147154 object_ids : dict [str , Any ]
@@ -5947,24 +5954,48 @@ def emit_log(
59475954 self ,
59485955 body : Any ,
59495956 level : LogLevel ,
5950- metadata : Metadata | None = None ,
5957+ metadata : dict [str , Any ] | None = None ,
5958+ ** parameters : object ,
59515959 ) -> str :
59525960 """Capture a log record, associating it with the active span when one exists.
59535961
59545962 The log is stored as an independent row. If a Braintrust or OpenTelemetry
59555963 span is active, the row reuses its span and trace IDs for correlation.
59565964 Otherwise, the row uses this logger's baseline trace ID.
59575965
5958- :param body: The log body. May be any JSON-serializable value.
5966+ String bodies may contain ``str.format``-style placeholders. Keyword
5967+ parameters are interpolated into the body and retained in metadata along
5968+ with the original template. Missing parameters remain as placeholders.
5969+
5970+ :param body: The log body. May be any JSON-serializable value when no
5971+ template parameters are provided.
59595972 :param level: The OpenTelemetry log severity: ``trace``, ``debug``,
59605973 ``info``, ``warn``, ``error``, or ``fatal``.
59615974 :param metadata: Optional JSON-serializable attributes for the log.
5975+ :param parameters: Values for named placeholders in a string body.
59625976 :returns: The unique ID of the captured log row.
59635977 """
59645978 if level not in _OTEL_LOG_LEVELS :
59655979 valid_levels = ", " .join (_OTEL_LOG_LEVELS )
59665980 raise ValueError (f"Invalid log level { level !r} . Expected one of: { valid_levels } " )
59675981
5982+ rendered_body = body
5983+ rendered_metadata = metadata
5984+ if parameters :
5985+ if not isinstance (body , str ):
5986+ raise TypeError ("Log body must be a string when template parameters are provided" )
5987+ rendered_metadata = dict (metadata ) if metadata is not None else {}
5988+ rendered_metadata .update (
5989+ {f"braintrust.template.parameter.{ key } " : value for key , value in parameters .items ()}
5990+ )
5991+ rendered_metadata ["braintrust.template" ] = body
5992+ try :
5993+ rendered_body = body .format_map (_LogTemplateParameters (parameters ))
5994+ except Exception :
5995+ # Logging should not disrupt the application because a template
5996+ # contains malformed braces or an unsupported format specifier.
5997+ rendered_body = body
5998+
59685999 captured_at = time .time ()
59696000 span_info = self .state .context_manager .get_current_span_info ()
59706001 severity_number = _OTEL_LOG_LEVELS [level ]
@@ -5976,9 +6007,13 @@ def emit_log(
59766007 span_id = span_info .span_id if span_info else None ,
59776008 root_span_id = span_info .trace_id if span_info else self ._baseline_trace_id ,
59786009 lookup_span_parent = False ,
5979- output = body ,
5980- error = body if severity_number >= _OTEL_LOG_LEVELS ["error" ] and isinstance (body , str ) else None ,
5981- metadata = metadata ,
6010+ output = rendered_body ,
6011+ error = (
6012+ rendered_body
6013+ if severity_number >= _OTEL_LOG_LEVELS ["error" ] and isinstance (rendered_body , str )
6014+ else None
6015+ ),
6016+ metadata = rendered_metadata ,
59826017 context = {
59836018 "otel" : {
59846019 "signal" : "logs" ,
@@ -5997,29 +6032,29 @@ def emit_log(
59976032
59986033 return span .id
59996034
6000- def trace (self , body : Any , metadata : Metadata | None = None ) -> str :
6035+ def trace (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60016036 """Capture a log at OpenTelemetry TRACE severity."""
6002- return self .emit_log (body = body , level = "trace" , metadata = metadata )
6037+ return self .emit_log (body = body , level = "trace" , metadata = metadata , ** parameters )
60036038
6004- def debug (self , body : Any , metadata : Metadata | None = None ) -> str :
6039+ def debug (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60056040 """Capture a log at OpenTelemetry DEBUG severity."""
6006- return self .emit_log (body = body , level = "debug" , metadata = metadata )
6041+ return self .emit_log (body = body , level = "debug" , metadata = metadata , ** parameters )
60076042
6008- def info (self , body : Any , metadata : Metadata | None = None ) -> str :
6043+ def info (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60096044 """Capture a log at OpenTelemetry INFO severity."""
6010- return self .emit_log (body = body , level = "info" , metadata = metadata )
6045+ return self .emit_log (body = body , level = "info" , metadata = metadata , ** parameters )
60116046
6012- def warn (self , body : Any , metadata : Metadata | None = None ) -> str :
6047+ def warn (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60136048 """Capture a log at OpenTelemetry WARN severity."""
6014- return self .emit_log (body = body , level = "warn" , metadata = metadata )
6049+ return self .emit_log (body = body , level = "warn" , metadata = metadata , ** parameters )
60156050
6016- def error (self , body : Any , metadata : Metadata | None = None ) -> str :
6051+ def error (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60176052 """Capture a log at OpenTelemetry ERROR severity."""
6018- return self .emit_log (body = body , level = "error" , metadata = metadata )
6053+ return self .emit_log (body = body , level = "error" , metadata = metadata , ** parameters )
60196054
6020- def fatal (self , body : Any , metadata : Metadata | None = None ) -> str :
6055+ def fatal (self , body : Any , metadata : dict [ str , Any ] | None = None , ** parameters : object ) -> str :
60216056 """Capture a log at OpenTelemetry FATAL severity."""
6022- return self .emit_log (body = body , level = "fatal" , metadata = metadata )
6057+ return self .emit_log (body = body , level = "fatal" , metadata = metadata , ** parameters )
60236058
60246059 def log_feedback (
60256060 self ,
0 commit comments