Error Handling

The Kibana Python client provides a comprehensive exception hierarchy for handling errors gracefully. Understanding and properly handling these exceptions is crucial for building robust applications.

Exception Hierarchy

KibanaException (base)
├── ApiError (API returned error)
│   ├── BadRequestError (400)
│   ├── AuthenticationException (401)
│   ├── AuthorizationException (403)
│   ├── NotFoundError (404)
│   └── ConflictError (409)
├── SpaceError (space-related error)
│   ├── SpaceNotFoundError (space doesn't exist)
│   └── InvalidSpaceIdError (invalid space ID format)
├── TransportError (transport-level error)
│   ├── ConnectionError (connection failed)
│   ├── ConnectionTimeout (connection timeout)
│   └── SSLError (SSL/TLS error)
└── SerializationError (serialization failed)

Common Exceptions

NotFoundError

Raised when a requested resource doesn’t exist.

from kibana import Kibana
from kibana.exceptions import NotFoundError

client = Kibana("http://localhost:5601", api_key="your_api_key")

try:
    connector = client.actions.get(id="nonexistent-id")
except NotFoundError as e:
    print(f"Connector not found: {e.message}")
    print(f"Status code: {e.status_code}")
    print(f"Response body: {e.body}")

AuthenticationException

Raised when authentication fails.

from kibana.exceptions import AuthenticationException

try:
    client = Kibana(
        "http://localhost:5601",
        api_key="invalid_key"
    )
    status = client.status.get_status()
except AuthenticationException as e:
    print(f"Authentication failed: {e.message}")
    # Handle invalid credentials

AuthorizationException

Raised when the user lacks required permissions.

from kibana.exceptions import AuthorizationException

try:
    spaces = client.spaces.get_all()
except AuthorizationException as e:
    print(f"Permission denied: {e.message}")
    # Handle insufficient permissions

BadRequestError

Raised when the request is malformed or invalid.

from kibana.exceptions import BadRequestError

try:
    connector = client.actions.create(
        name="Test",
        connector_type_id=".invalid-type",  # Invalid type
        config={}
    )
except BadRequestError as e:
    print(f"Invalid request: {e.message}")
    print(f"Details: {e.body}")

ConflictError

Raised when there’s a conflict (e.g., duplicate resource, version mismatch).

from kibana.exceptions import ConflictError

try:
    # Update with wrong version
    client.saved_objects.update(
        type="dashboard",
        id="dash-1",
        attributes={"title": "New Title"},
        version="wrong-version"
    )
except ConflictError as e:
    print(f"Version conflict: {e.message}")
    # Fetch latest version and retry

SpaceNotFoundError

Raised when a specified space doesn’t exist.

from kibana.exceptions import SpaceNotFoundError

try:
    connector = client.actions.create(
        name="Test",
        connector_type_id=".index",
        config={"index": "test"},
        space_id="nonexistent-space"
    )
except SpaceNotFoundError as e:
    print(f"Space not found: {e.space_id}")
    # Create the space or use a different one

ConnectionError

Raised when connection to Kibana fails.

from kibana.exceptions import ConnectionError

try:
    client = Kibana("http://invalid-host:5601")
    status = client.status.get_status()
except ConnectionError as e:
    print(f"Connection failed: {e.message}")
    # Handle connection failure

Error Handling Patterns

Basic Try-Except

from kibana import Kibana
from kibana.exceptions import KibanaException

client = Kibana("http://localhost:5601", api_key="your_api_key")

try:
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )
    print(f"Created: {connector.body['id']}")

except KibanaException as e:
    print(f"Error: {e.message}")
    print(f"Status: {e.status_code if hasattr(e, 'status_code') else 'N/A'}")
finally:
    client.close()

Specific Exception Handling

from kibana.exceptions import (
    NotFoundError,
    ConflictError,
    BadRequestError,
    AuthenticationException,
    SpaceNotFoundError
)

try:
    connector = client.actions.create(
        name="Test Connector",
        connector_type_id=".index",
        config={"index": "test"},
        space_id="marketing"
    )

except SpaceNotFoundError as e:
    print(f"Space '{e.space_id}' does not exist")
    # Create the space
    client.spaces.create(id=e.space_id, name="Marketing")
    # Retry operation

except ConflictError as e:
    print(f"Connector already exists: {e.message}")
    # Get existing connector

except BadRequestError as e:
    print(f"Invalid configuration: {e.message}")
    # Fix configuration and retry

except AuthenticationException as e:
    print(f"Authentication failed: {e.message}")
    # Refresh credentials

except NotFoundError as e:
    print(f"Resource not found: {e.message}")
    # Handle missing resource

Retry Pattern

import time
from kibana.exceptions import ApiError, ConnectionError

def create_connector_with_retry(client, max_retries=3):
    """Create connector with retry logic."""
    for attempt in range(max_retries):
        try:
            connector = client.actions.create(
                name="My Connector",
                connector_type_id=".index",
                config={"index": "my-index"}
            )
            return connector

        except ConnectionError as e:
            if attempt == max_retries - 1:
                raise

            wait_time = 2 ** attempt  # Exponential backoff
            print(f"Connection failed, retrying in {wait_time}s...")
            time.sleep(wait_time)

        except ApiError as e:
            if e.status_code >= 500:  # Server error
                if attempt == max_retries - 1:
                    raise

                wait_time = 2 ** attempt
                print(f"Server error, retrying in {wait_time}s...")
                time.sleep(wait_time)
            else:
                # Client error, don't retry
                raise

# Usage
try:
    connector = create_connector_with_retry(client)
    print(f"Created: {connector.body['id']}")
except Exception as e:
    print(f"Failed after retries: {e}")

Fallback Pattern

def get_connector_with_fallback(client, connector_id, space_id=None):
    """Get connector with fallback to default space."""
    try:
        # Try to get from specified space
        return client.actions.get(id=connector_id, space_id=space_id)

    except SpaceNotFoundError:
        print(f"Space '{space_id}' not found, trying default space")
        # Fallback to default space
        return client.actions.get(id=connector_id)

    except NotFoundError:
        print(f"Connector '{connector_id}' not found in any space")
        return None

Context Manager Pattern

from contextlib import contextmanager

@contextmanager
def kibana_operation(client, operation_name):
    """Context manager for Kibana operations with error handling."""
    try:
        print(f"Starting {operation_name}...")
        yield
        print(f"Completed {operation_name}")

    except NotFoundError as e:
        print(f"{operation_name} failed: Resource not found - {e.message}")
        raise

    except ConflictError as e:
        print(f"{operation_name} failed: Conflict - {e.message}")
        raise

    except ApiError as e:
        print(f"{operation_name} failed: API error - {e.message}")
        raise

    except Exception as e:
        print(f"{operation_name} failed: Unexpected error - {e}")
        raise

# Usage
with kibana_operation(client, "Create connector"):
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )

Error Recovery Strategies

Automatic Retry with Exponential Backoff

import time
from functools import wraps

def retry_on_error(max_retries=3, backoff_factor=2):
    """Decorator for automatic retry with exponential backoff."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except (ConnectionError, ApiError) as e:
                    if attempt == max_retries - 1:
                        raise

                    if isinstance(e, ApiError) and e.status_code < 500:
                        # Don't retry client errors
                        raise

                    wait_time = backoff_factor ** attempt
                    print(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
                    time.sleep(wait_time)

        return wrapper
    return decorator

# Usage
@retry_on_error(max_retries=3)
def create_connector(client):
    return client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )

Circuit Breaker Pattern

import time

class CircuitBreaker:
    """Circuit breaker for Kibana operations."""

    def __init__(self, failure_threshold=5, timeout=60):
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.failures = 0
        self.last_failure_time = None
        self.state = 'closed'  # closed, open, half-open

    def call(self, func, *args, **kwargs):
        """Execute function with circuit breaker."""
        if self.state == 'open':
            if time.time() - self.last_failure_time > self.timeout:
                self.state = 'half-open'
            else:
                raise Exception("Circuit breaker is open")

        try:
            result = func(*args, **kwargs)
            self.on_success()
            return result

        except Exception as e:
            self.on_failure()
            raise

    def on_success(self):
        """Handle successful call."""
        self.failures = 0
        self.state = 'closed'

    def on_failure(self):
        """Handle failed call."""
        self.failures += 1
        self.last_failure_time = time.time()

        if self.failures >= self.failure_threshold:
            self.state = 'open'

# Usage
breaker = CircuitBreaker(failure_threshold=5, timeout=60)

try:
    connector = breaker.call(
        client.actions.create,
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )
except Exception as e:
    print(f"Circuit breaker prevented call or operation failed: {e}")

Logging Errors

Basic Error Logging

import logging

logger = logging.getLogger(__name__)

try:
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )
except KibanaException as e:
    logger.error(
        "Failed to create connector",
        extra={
            'error_type': type(e).__name__,
            'error_message': str(e),
            'status_code': getattr(e, 'status_code', None),
            'connector_name': "My Connector"
        }
    )
    raise

Structured Error Logging

import logging
import traceback

logger = logging.getLogger(__name__)

def log_error(operation, error, context=None):
    """Log error with structured information."""
    error_info = {
        'operation': operation,
        'error_type': type(error).__name__,
        'error_message': str(error),
        'traceback': traceback.format_exc()
    }

    if hasattr(error, 'status_code'):
        error_info['status_code'] = error.status_code

    if hasattr(error, 'body'):
        error_info['response_body'] = error.body

    if context:
        error_info.update(context)

    logger.error(f"Operation failed: {operation}", extra=error_info)

# Usage
try:
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"},
        space_id="marketing"
    )
except KibanaException as e:
    log_error(
        "create_connector",
        e,
        context={
            'connector_name': "My Connector",
            'connector_type': ".index",
            'space_id': "marketing"
        }
    )
    raise

Best Practices

1. Always Use Specific Exceptions

# Good: Specific exception handling
try:
    connector = client.actions.get(id="conn-1")
except NotFoundError:
    # Handle not found
    pass
except AuthorizationException:
    # Handle permission denied
    pass

# Avoid: Catching all exceptions
try:
    connector = client.actions.get(id="conn-1")
except Exception:
    # Too broad
    pass

2. Provide Context in Error Messages

# Good: Contextual error handling
try:
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"},
        space_id="marketing"
    )
except SpaceNotFoundError as e:
    print(f"Cannot create connector in space '{e.space_id}': space does not exist")
except ConflictError as e:
    print(f"Connector 'My Connector' already exists in space 'marketing'")

3. Clean Up Resources

# Good: Always clean up
connector = None
try:
    connector = client.actions.create(
        name="Temp Connector",
        connector_type_id=".index",
        config={"index": "temp"}
    )
    # Use connector
    result = client.actions.execute(
        id=connector.body["id"],
        params={"documents": [{"test": "data"}]}
    )
finally:
    # Clean up even if error occurs
    if connector:
        try:
            client.actions.delete(id=connector.body["id"])
        except Exception:
            pass

4. Use Context Managers

# Good: Automatic cleanup
with Kibana("http://localhost:5601", api_key="your_api_key") as client:
    connector = client.actions.create(
        name="My Connector",
        connector_type_id=".index",
        config={"index": "my-index"}
    )
    # Client is automatically closed

Troubleshooting

Debugging Errors

Enable debug logging to see detailed error information:

import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("kibana")
logger.setLevel(logging.DEBUG)

# Now you'll see detailed request/response information
try:
    connector = client.actions.create(
        name="Debug Connector",
        connector_type_id=".index",
        config={"index": "debug"}
    )
except Exception as e:
    # Detailed error information will be logged
    print(f"Error: {e}")

Common Error Scenarios

  1. Authentication Failures: Check API key/credentials

  2. Permission Denied: Verify user has required privileges

  3. Resource Not Found: Verify IDs and space context

  4. Connection Issues: Check network and Kibana availability

  5. Version Conflicts: Fetch latest version before updating

Next Steps