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
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¶
Authentication Failures: Check API key/credentials
Permission Denied: Verify user has required privileges
Resource Not Found: Verify IDs and space context
Connection Issues: Check network and Kibana availability
Version Conflicts: Fetch latest version before updating
Next Steps¶
Learn about Observability for distributed tracing
Explore Advanced Usage for performance optimization
Check Status Monitoring for health checks
See Examples for practical code samples