Skip to content

honeyhive.utils.logger

HoneyHive Logging Module - Structured logging utilities.

default_logger module-attribute

default_logger = get_logger('honeyhive')

HoneyHiveFormatter

Bases: Formatter

Custom formatter for HoneyHive logs.

Provides structured JSON logging with configurable fields including timestamps, log levels, and HoneyHive-specific data.

Source code in src/honeyhive/utils/logger.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class HoneyHiveFormatter(logging.Formatter):
    """Custom formatter for HoneyHive logs.

    Provides structured JSON logging with configurable fields
    including timestamps, log levels, and HoneyHive-specific data.
    """

    def __init__(
        self, include_timestamp: bool = True, include_level: bool = True
    ) -> None:
        """Initialize the formatter.

        Args:
            include_timestamp: Whether to include timestamp in log output
            include_level: Whether to include log level in log output
        """
        super().__init__()
        self.include_timestamp = include_timestamp
        self.include_level = include_level

    def format(self, record: logging.LogRecord) -> str:
        """Format log record with HoneyHive structure.

        Args:
            record: Log record to format

        Returns:
            JSON-formatted log string
        """
        log_data = {
            "timestamp": (
                datetime.now(timezone.utc).isoformat()
                if self.include_timestamp
                else None
            ),
            "level": record.levelname if self.include_level else None,
            "logger": record.name,
            "message": record.getMessage(),
        }

        # Add extra fields if present
        if hasattr(record, "honeyhive_data"):
            log_data.update(getattr(record, "honeyhive_data", {}))

        # Add exception info if present
        if record.exc_info:
            log_data["exception"] = self.formatException(record.exc_info)

        # Remove None values
        log_data = {k: v for k, v in log_data.items() if v is not None}

        return json.dumps(log_data, default=str)

include_timestamp instance-attribute

include_timestamp = include_timestamp

include_level instance-attribute

include_level = include_level

format

format(record: LogRecord) -> str

Format log record with HoneyHive structure.

Parameters:

Name Type Description Default
record LogRecord

Log record to format

required

Returns:

Type Description
str

JSON-formatted log string

Source code in src/honeyhive/utils/logger.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def format(self, record: logging.LogRecord) -> str:
    """Format log record with HoneyHive structure.

    Args:
        record: Log record to format

    Returns:
        JSON-formatted log string
    """
    log_data = {
        "timestamp": (
            datetime.now(timezone.utc).isoformat()
            if self.include_timestamp
            else None
        ),
        "level": record.levelname if self.include_level else None,
        "logger": record.name,
        "message": record.getMessage(),
    }

    # Add extra fields if present
    if hasattr(record, "honeyhive_data"):
        log_data.update(getattr(record, "honeyhive_data", {}))

    # Add exception info if present
    if record.exc_info:
        log_data["exception"] = self.formatException(record.exc_info)

    # Remove None values
    log_data = {k: v for k, v in log_data.items() if v is not None}

    return json.dumps(log_data, default=str)

HoneyHiveLogger

HoneyHive logger with structured logging.

Provides a structured logging interface with HoneyHive-specific formatting and context data support. Uses per-instance configuration instead of global config for multi-instance architecture.

Source code in src/honeyhive/utils/logger.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
class HoneyHiveLogger:
    """HoneyHive logger with structured logging.

    Provides a structured logging interface with HoneyHive-specific
    formatting and context data support. Uses per-instance configuration
    instead of global config for multi-instance architecture.
    """

    def __init__(  # pylint: disable=too-many-arguments
        self,
        name: str,
        *,
        level: Optional[Union[str, int]] = None,
        formatter: Optional[logging.Formatter] = None,
        handler: Optional[logging.Handler] = None,
        verbose: Optional[bool] = None,
    ):
        """Initialize the logger.

        Note: too-many-positional-arguments disabled - Logger class requires multiple
        configuration parameters (name, level, formatter, handler, verbose) for
        proper initialization and flexibility.

        Args:
            name: Logger name
            level: Log level (string or integer)
            formatter: Custom formatter to use
            handler: Custom handler to use
            verbose: Whether to enable debug logging (overrides level if provided)
        """
        self.logger = logging.getLogger(name)
        self.verbose = verbose

        # Dynamic level determination with verbose parameter priority
        effective_level = self._determine_log_level_dynamically(level, verbose)
        self.logger.setLevel(effective_level)

        # Add handler if not already present
        if not self.logger.handlers:
            if handler is None:
                handler = logging.StreamHandler(sys.stdout)
                if formatter is None:
                    formatter = HoneyHiveFormatter()
                handler.setFormatter(formatter)
            self.logger.addHandler(handler)

        # Prevent propagation to root logger
        self.logger.propagate = False

    def _determine_log_level_dynamically(
        self, level: Optional[Union[str, int]], verbose: Optional[bool]
    ) -> int:
        """Dynamically determine the appropriate log level.

        Uses dynamic logic to prioritize:
        1. Explicit level parameter
        2. Verbose parameter (True = DEBUG, False = WARNING)
        3. Default to WARNING

        Args:
            level: Explicit log level
            verbose: Verbose flag from tracer

        Returns:
            Resolved log level as integer
        """
        # Priority 1: Explicit level parameter
        if level is not None:
            if isinstance(level, str):
                return getattr(logging, level.upper(), logging.WARNING)
            if isinstance(level, int):
                return level

        # Priority 2: Verbose parameter from tracer
        if verbose is True:
            return logging.DEBUG
        if verbose is False:
            return logging.WARNING

        # Priority 3: Default to WARNING (suppress INFO/DEBUG, show
        # WARNING/ERROR/CRITICAL)
        return logging.WARNING

    def update_verbose_setting(self, verbose: bool) -> None:
        """Dynamically update the logger's verbose setting.

        This allows the tracer to update the logger's level
        after initialization based on configuration changes.

        Args:
            verbose: New verbose setting
        """
        self.verbose = verbose
        new_level = logging.DEBUG if verbose else logging.WARNING
        self.logger.setLevel(new_level)

    def _log_with_context(
        self,
        level: int,
        message: str,
        args: tuple = (),
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log with HoneyHive context data and lazy formatting support.

        Args:
            level: Log level
            message: Log message format string
            args: Arguments for lazy string formatting
            honeyhive_data: Additional HoneyHive context data
            **kwargs: Additional logging parameters
        """
        extra = kwargs.copy()
        if honeyhive_data:
            extra["honeyhive_data"] = honeyhive_data

        # Use Python's standard logging lazy formatting
        self.logger.log(level, message, *args, extra=extra)

    def debug(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log debug message with lazy formatting support.

        Args:
            message: Debug message format string (supports % formatting)
            *args: Arguments for lazy string formatting
            honeyhive_data: Additional HoneyHive context data
            **kwargs: Additional logging parameters
        """
        self._log_with_context(logging.DEBUG, message, args, honeyhive_data, **kwargs)

    def info(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log info message with lazy formatting support."""
        self._log_with_context(logging.INFO, message, args, honeyhive_data, **kwargs)

    def warning(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log warning message with lazy formatting support."""
        self._log_with_context(logging.WARNING, message, args, honeyhive_data, **kwargs)

    def error(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log error message with lazy formatting support."""
        self._log_with_context(logging.ERROR, message, args, honeyhive_data, **kwargs)

    def critical(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log critical message with lazy formatting support."""
        self._log_with_context(
            logging.CRITICAL, message, args, honeyhive_data, **kwargs
        )

    def exception(
        self,
        message: str,
        *args: Any,
        honeyhive_data: Optional[Dict[str, Any]] = None,
        **kwargs: Any,
    ) -> None:
        """Log exception message with traceback and lazy formatting support."""
        extra = kwargs.copy()
        if honeyhive_data:
            extra["honeyhive_data"] = honeyhive_data

        self.logger.exception(message, *args, extra=extra)

logger instance-attribute

logger = getLogger(name)

verbose instance-attribute

verbose = verbose

update_verbose_setting

update_verbose_setting(verbose: bool) -> None

Dynamically update the logger's verbose setting.

This allows the tracer to update the logger's level after initialization based on configuration changes.

Parameters:

Name Type Description Default
verbose bool

New verbose setting

required
Source code in src/honeyhive/utils/logger.py
213
214
215
216
217
218
219
220
221
222
223
224
def update_verbose_setting(self, verbose: bool) -> None:
    """Dynamically update the logger's verbose setting.

    This allows the tracer to update the logger's level
    after initialization based on configuration changes.

    Args:
        verbose: New verbose setting
    """
    self.verbose = verbose
    new_level = logging.DEBUG if verbose else logging.WARNING
    self.logger.setLevel(new_level)

debug

debug(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log debug message with lazy formatting support.

Parameters:

Name Type Description Default
message str

Debug message format string (supports % formatting)

required
*args Any

Arguments for lazy string formatting

()
honeyhive_data Optional[Dict[str, Any]]

Additional HoneyHive context data

None
**kwargs Any

Additional logging parameters

{}
Source code in src/honeyhive/utils/logger.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def debug(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log debug message with lazy formatting support.

    Args:
        message: Debug message format string (supports % formatting)
        *args: Arguments for lazy string formatting
        honeyhive_data: Additional HoneyHive context data
        **kwargs: Additional logging parameters
    """
    self._log_with_context(logging.DEBUG, message, args, honeyhive_data, **kwargs)

info

info(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log info message with lazy formatting support.

Source code in src/honeyhive/utils/logger.py
267
268
269
270
271
272
273
274
275
def info(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log info message with lazy formatting support."""
    self._log_with_context(logging.INFO, message, args, honeyhive_data, **kwargs)

warning

warning(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log warning message with lazy formatting support.

Source code in src/honeyhive/utils/logger.py
277
278
279
280
281
282
283
284
285
def warning(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log warning message with lazy formatting support."""
    self._log_with_context(logging.WARNING, message, args, honeyhive_data, **kwargs)

error

error(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log error message with lazy formatting support.

Source code in src/honeyhive/utils/logger.py
287
288
289
290
291
292
293
294
295
def error(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log error message with lazy formatting support."""
    self._log_with_context(logging.ERROR, message, args, honeyhive_data, **kwargs)

critical

critical(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log critical message with lazy formatting support.

Source code in src/honeyhive/utils/logger.py
297
298
299
300
301
302
303
304
305
306
307
def critical(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log critical message with lazy formatting support."""
    self._log_with_context(
        logging.CRITICAL, message, args, honeyhive_data, **kwargs
    )

exception

exception(
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Log exception message with traceback and lazy formatting support.

Source code in src/honeyhive/utils/logger.py
309
310
311
312
313
314
315
316
317
318
319
320
321
def exception(
    self,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Log exception message with traceback and lazy formatting support."""
    extra = kwargs.copy()
    if honeyhive_data:
        extra["honeyhive_data"] = honeyhive_data

    self.logger.exception(message, *args, extra=extra)

is_shutdown_detected

is_shutdown_detected() -> bool

Check if shutdown has been detected (for internal use by tracer components).

This function dynamically detects shutdown conditions and is safe to call from any tracer component that needs to check shutdown state.

Returns:

Type Description
bool

True if shutdown is in progress, False otherwise

Source code in src/honeyhive/utils/logger.py
55
56
57
58
59
60
61
62
63
64
def is_shutdown_detected() -> bool:
    """Check if shutdown has been detected (for internal use by tracer components).

    This function dynamically detects shutdown conditions and is safe to call
    from any tracer component that needs to check shutdown state.

    Returns:
        True if shutdown is in progress, False otherwise
    """
    return _detect_shutdown_conditions()

reset_logging_state

reset_logging_state() -> None

Reset logging state (primarily for testing).

This function clears all internal state, which is useful for testing scenarios where logging state needs to be reset.

Source code in src/honeyhive/utils/logger.py
67
68
69
70
71
72
73
def reset_logging_state() -> None:
    """Reset logging state (primarily for testing).

    This function clears all internal state, which is useful
    for testing scenarios where logging state needs to be reset.
    """
    _shutdown_detected.clear()

get_logger

get_logger(
    name: str,
    verbose: Optional[bool] = None,
    tracer_instance: Optional[Any] = None,
    **kwargs: Any
) -> HoneyHiveLogger

Get a HoneyHive logger instance with dynamic configuration.

Uses dynamic logic to determine logger configuration based on tracer instance settings or explicit parameters.

Parameters:

Name Type Description Default
name str

Logger name

required
verbose Optional[bool]

Explicit verbose setting

None
tracer_instance Optional[Any]

Tracer instance to extract verbose setting from

None
**kwargs Any

Additional logger parameters

{}

Returns:

Type Description
HoneyHiveLogger

Configured HoneyHive logger instance

Source code in src/honeyhive/utils/logger.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
def get_logger(
    name: str,
    verbose: Optional[bool] = None,
    tracer_instance: Optional[Any] = None,
    **kwargs: Any,
) -> HoneyHiveLogger:
    """Get a HoneyHive logger instance with dynamic configuration.

    Uses dynamic logic to determine logger configuration based on
    tracer instance settings or explicit parameters.

    Args:
        name: Logger name
        verbose: Explicit verbose setting
        tracer_instance: Tracer instance to extract verbose setting from
        **kwargs: Additional logger parameters

    Returns:
        Configured HoneyHive logger instance
    """
    # Dynamic verbose detection from tracer instance
    if verbose is None and tracer_instance is not None:
        verbose = _extract_verbose_from_tracer_dynamically(tracer_instance)

    return HoneyHiveLogger(name, verbose=verbose, **kwargs)

get_tracer_logger

get_tracer_logger(
    tracer_instance: Any, logger_name: Optional[str] = None
) -> HoneyHiveLogger

Get a logger instance configured for a specific tracer.

Creates a unique logger per tracer instance with dynamic configuration based on the tracer's verbose setting.

Parameters:

Name Type Description Default
tracer_instance Any

Tracer instance to create logger for

required
logger_name Optional[str]

Optional custom logger name

None

Returns:

Type Description
HoneyHiveLogger

Logger configured for the tracer instance

Source code in src/honeyhive/utils/logger.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
def get_tracer_logger(
    tracer_instance: Any, logger_name: Optional[str] = None
) -> HoneyHiveLogger:
    """Get a logger instance configured for a specific tracer.

    Creates a unique logger per tracer instance with dynamic configuration
    based on the tracer's verbose setting.

    Args:
        tracer_instance: Tracer instance to create logger for
        logger_name: Optional custom logger name

    Returns:
        Logger configured for the tracer instance
    """
    # Generate unique logger name per tracer instance
    if logger_name is None:
        tracer_id = getattr(tracer_instance, "tracer_id", id(tracer_instance))
        logger_name = f"honeyhive.tracer.{tracer_id}"

    return get_logger(name=logger_name, tracer_instance=tracer_instance)

safe_log

safe_log(
    tracer_instance: Any,
    level: str,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any
) -> None

Safely log a message with enhanced early initialization and multi-instance support.

This function provides comprehensive protection against logging failures during: - Python interpreter shutdown - Stream closure in parallel/multiprocess execution - Thread teardown race conditions - Container/serverless environment shutdown - Early initialization before tracer logger is ready - Multi-instance tracer scenarios

Enhanced Fallback Strategy: 1. Use tracer instance logger if fully initialized 2. Delegate to actual tracer if tracer_instance has tracer_instance (API client pattern) 3. Use tracer_instance's own logger if available (API client independent mode) 4. Create temporary logger with tracer's verbose setting if tracer exists but logger not ready 5. Use default fallback logger for None tracer_instance or early initialization

Parameters:

Name Type Description Default
tracer_instance Any

Optional tracer instance for per-instance logging (can be None or partially initialized)

required
level str

Log level (debug, info, warning, error)

required
message str

Log message format string (supports % formatting for lazy evaluation)

required
*args Any

Arguments for lazy string formatting (deferred until log level check)

()
honeyhive_data Optional[Dict[str, Any]]

Optional structured data for HoneyHive logger

None
**kwargs Any

Additional keyword arguments for logger

{}
Performance Note

Uses lazy formatting with % placeholders for optimal performance. String interpolation is deferred until the log level is confirmed active, avoiding unnecessary string operations for filtered log messages.

Example

safe_log(tracer_instance, "debug", "Processing %s spans", span_count, ... honeyhive_data={"span_id": "123"}) safe_log(tracer_instance, "warning", ... "Failed to process %s after %d tries", ... item_name, retry_count)

✅ CORRECT - Static messages

safe_log(None, "info", "Static message") # Works without tracer safe_log(partial_tracer, "debug", "Early init message")

❌ AVOID - F-strings (eager evaluation, performance impact)

safe_log(tracer, "error", f"Failed: {error}") # Don't do this

Source code in src/honeyhive/utils/logger.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
def safe_log(
    tracer_instance: Any,
    level: str,
    message: str,
    *args: Any,
    honeyhive_data: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> None:
    """Safely log a message with enhanced early initialization and
    multi-instance support.

    This function provides comprehensive protection against logging failures during:
    - Python interpreter shutdown
    - Stream closure in parallel/multiprocess execution
    - Thread teardown race conditions
    - Container/serverless environment shutdown
    - Early initialization before tracer logger is ready
    - Multi-instance tracer scenarios

    Enhanced Fallback Strategy:
    1. Use tracer instance logger if fully initialized
    2. Delegate to actual tracer if tracer_instance has tracer_instance
       (API client pattern)
    3. Use tracer_instance's own logger if available
       (API client independent mode)
    4. Create temporary logger with tracer's verbose setting if tracer exists
       but logger not ready
    5. Use default fallback logger for None tracer_instance or early
       initialization

    Args:
        tracer_instance: Optional tracer instance for per-instance logging
            (can be None or partially initialized)
        level: Log level (debug, info, warning, error)
        message: Log message format string (supports % formatting for lazy evaluation)
        *args: Arguments for lazy string formatting (deferred until log level check)
        honeyhive_data: Optional structured data for HoneyHive logger
        **kwargs: Additional keyword arguments for logger

    Performance Note:
        Uses lazy formatting with % placeholders for optimal performance.
        String interpolation is deferred until the log level is confirmed active,
        avoiding unnecessary string operations for filtered log messages.

    Example:
        >>> # ✅ CORRECT - Lazy formatting (recommended)
        >>> safe_log(tracer_instance, "debug", "Processing %s spans", span_count,
        ...          honeyhive_data={"span_id": "123"})
        >>> safe_log(tracer_instance, "warning",
        ...           "Failed to process %s after %d tries",
        ...          item_name, retry_count)
        >>> # ✅ CORRECT - Static messages
        >>> safe_log(None, "info", "Static message")  # Works without tracer
        >>> safe_log(partial_tracer, "debug", "Early init message")
        >>> # ❌ AVOID - F-strings (eager evaluation, performance impact)
        >>> # safe_log(tracer, "error", f"Failed: {error}")  # Don't do this
    """
    # Import here to avoid circular imports

    # Skip all logging if shutdown conditions are detected
    if _detect_shutdown_conditions():
        return None

    try:
        # Enhanced fallback logic for early initialization and multi-instance safety
        target_logger = None

        # Strategy 1: Use tracer instance logger if fully initialized
        if (
            tracer_instance
            and hasattr(tracer_instance, "logger")
            and tracer_instance.logger
        ):
            target_logger = tracer_instance.logger

        # Strategy 2: Check if tracer_instance has its own tracer_instance
        # (API client pattern)
        elif (
            tracer_instance
            and hasattr(tracer_instance, "tracer_instance")
            and tracer_instance.tracer_instance
        ):
            # API client with tracer_instance - delegate to the actual tracer
            return safe_log(
                tracer_instance.tracer_instance,
                level,
                message,
                *args,
                honeyhive_data=honeyhive_data,
                **kwargs,
            )

        # Strategy 3: Use tracer_instance's own logger if it has one
        # (API client independent mode)
        elif (
            tracer_instance
            and hasattr(tracer_instance, "logger")
            and tracer_instance.logger
        ):
            target_logger = tracer_instance.logger

        # Strategy 4: Use tracer instance for logger creation if partially
        # initialized
        elif tracer_instance and hasattr(tracer_instance, "verbose"):
            # Tracer exists but logger not ready - create temporary logger with
            # tracer's verbose setting
            verbose_setting = getattr(tracer_instance, "verbose", False)
            target_logger = get_logger("honeyhive.early_init", verbose=verbose_setting)

        # Strategy 5: Fallback to default logger for None tracer_instance or
        # no verbose setting
        else:
            # Complete fallback for early initialization or None tracer_instance
            target_logger = get_logger("honeyhive.fallback")

        # Check if the logger and its handlers are still available
        if not hasattr(target_logger, "logger") or not target_logger.logger.handlers:
            return None  # Logger is gone, fail silently

        # Check if any handler has a closed stream
        for handler in target_logger.logger.handlers:
            if hasattr(handler, "stream") and hasattr(
                getattr(handler, "stream", None), "closed"
            ):
                stream = getattr(handler, "stream", None)
                if stream and getattr(stream, "closed", False):
                    # Mark shutdown detected for future calls
                    _shutdown_detected.set()
                    return None  # Stream is closed, fail silently

        log_func = getattr(target_logger, level)
        if honeyhive_data:
            log_func(message, *args, honeyhive_data=honeyhive_data, **kwargs)
        else:
            log_func(message, *args, **kwargs)

    except Exception:
        # Fail silently - logging should never crash the application
        # This includes cases where:
        # - Logger methods don't exist
        # - Stream operations fail
        # - Handler operations fail
        # - Any other logging-related exception
        pass

    return None

safe_debug

safe_debug(
    tracer_instance: Any, message: str, **kwargs: Any
) -> None

Convenience function for debug logging.

Source code in src/honeyhive/utils/logger.py
564
565
566
def safe_debug(tracer_instance: Any, message: str, **kwargs: Any) -> None:
    """Convenience function for debug logging."""
    safe_log(tracer_instance, "debug", message, **kwargs)

safe_info

safe_info(
    tracer_instance: Any, message: str, **kwargs: Any
) -> None

Convenience function for info logging.

Source code in src/honeyhive/utils/logger.py
569
570
571
def safe_info(tracer_instance: Any, message: str, **kwargs: Any) -> None:
    """Convenience function for info logging."""
    safe_log(tracer_instance, "info", message, **kwargs)

safe_warning

safe_warning(
    tracer_instance: Any, message: str, **kwargs: Any
) -> None

Convenience function for warning logging.

Source code in src/honeyhive/utils/logger.py
574
575
576
def safe_warning(tracer_instance: Any, message: str, **kwargs: Any) -> None:
    """Convenience function for warning logging."""
    safe_log(tracer_instance, "warning", message, **kwargs)

safe_error

safe_error(
    tracer_instance: Any, message: str, **kwargs: Any
) -> None

Convenience function for error logging.

Source code in src/honeyhive/utils/logger.py
579
580
581
def safe_error(tracer_instance: Any, message: str, **kwargs: Any) -> None:
    """Convenience function for error logging."""
    safe_log(tracer_instance, "error", message, **kwargs)